【问题标题】:Async unit tests not working as expected异步单元测试未按预期工作
【发布时间】:2012-11-14 19:23:55
【问题描述】:

我在 Visual Studio 2012 中使用了最新版本的 NUnit (2.6.2),同时使用了 resharper 和 Visual Studio 测试运行程序。我有以下示例测试,我在其中尝试验证预期的异步方法调用是否引发了异常。

不幸的是,这似乎没有按预期工作。第一个测试AsyncTaskCanceledSemiWorking 只起作用,因为我有 expectedexception 属性。实际的断言被完全忽略(正如您可以从 ArgumentOutOfRange 异常中看到的那样,它只是假装让它失败)。

AsyncTaskCanceledWorking 工作正常,但不测试是否在指定行上抛出异常,因此用处不大。

第三个失败了,下面……

System.Threading.Tasks.TaskCanceledException : A task was canceled.
Exception doesn't have a stacktrace

任何关于如何从特定行测试 TaskCanceledException 的想法都会非常有用。

谢谢

    [Test]
    [ExpectedException(typeof(TaskCanceledException))]
    public async Task AsyncTaskCanceledSemiWorking()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        Assert.That(await LongRunningFunction(token), Throws.InstanceOf<ArgumentOutOfRangeException>());


    }

    [Test]
    [ExpectedException(typeof(TaskCanceledException))]
    public async Task AsyncTaskCanceledWorking()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        int i = await LongRunningFunction(token);
    } 


    [Test]
    public async Task AsyncTaskCanceledFailed()
    {
        CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;

        cancellationTokenSource.Cancel();

        Assert.That(await LongRunningFunction(token), Throws.InstanceOf<TaskCanceledException>());

    }

    public async Task<int> LongRunningFunction(CancellationToken token)
    {
        token.ThrowIfCancellationRequested();

        await Task.Delay(1000, token);

        return 5;
    } 

【问题讨论】:

  • 嗨彼得,知道你会期望什么行为会很有帮助。

标签: c# unit-testing nunit async-await


【解决方案1】:

我假设您想检查 LongRunningFunction 是否会抛出 TaskCanceledException

我认为你所遇到的行为是完全正确的,误解就在这句话中:

await LongRunningFunction(token)

在这里,您正在有效地执行异步操作并等待它完成,这也将重新引发在其调用中发生的第一个异常。您基本上可以将其替换为:

throw new TaskCanceledException()

因此,为什么前两个测试成功 - 您使用的是 ExpectedExceptionAttribute - 而第三个失败 - 您没有预料到异常。

Assert.That 的第一个参数,当您稍后使用 Throws 时,应该是某种委托,因为 NUnit 必须调用它才能捕获从其调用中冒出的异常。如果您自己调用它,那么 NUnit 除了使用 ExpectedExceptionAttribute 之外当然无法捕获异常。

换句话说,理想的正确方法是:

// WARNING: this code does not work in NUnit <= 2.6.2
Assert.That(async () => await LongRunningFunction(token), Throws.InstanceOf<TaskCanceledException>());

我想告诉你,NUnit 支持异步方法的这种语法,这很自然,并且允许你在代码的特定部分测试异常,但它不支持并且测试会失败报告你期待一个异常,但没有发生异常。

原因是为了从调用该异步匿名方法中获取异常,NUnit 必须等待它,而它目前没有。

我可以为您提供的另一种选择是使用非异步 lambda,您在其中 Wait 处理异步操作返回的任务,但遗憾的是语法不如 awaiting 好异步操作的行为方式与它返回的任务的等待不同。具体来说,在异步操作引发异常的情况下,您将在第一种情况下获得实际异常,在第二种情况下获得AggregateException。无论如何,这里有一些适用于 2.6.2 的代码:

var aggregate = Assert.Throws<AggregateException>(() => LongRunningFunction(token).Wait());
Assert.IsInstanceOf<TaskCanceledException>(aggregate.InnerExceptions.Single());

总而言之,虽然 NUnit 2.6.2 确实引入了对异步测试方法的支持,允许您编写 async [void|Task|Task&lt;T&gt;] 测试,但我们没有考虑将支持扩展到异步匿名方法,这在这种情况下会很有用断言,尽管我相信我们可以。

【讨论】:

  • 我想等待一个抛出异常的方法并断言它抛出异常。我相信这就是你说我实际上需要一个代表的地方。你认为 NUnit 会很快支持这个吗?感谢回复
  • 我不能肯定地告诉你,我只能说这是可能的。请允许我指出,你的第一个陈述是矛盾的。如果您等待一个操作,您不能断言它会引发异常,除非您使用可以捕获异常的代码来包装调用,否则异常只会冒泡,这就是您的示例中发生的情况。
【解决方案2】:

如果您将 Resharper 7.1 或更高版本与 NUnit 2.6.2 或更高版本一起使用,public async void 的测试方法将起作用。 Resharper 7.1 于今天(2012 年 11 月 13 日)发布

Resharper 测试运行器的早期版本不等待测试完成,无需实际测试任何内容即可通过。

2.6.2 之前的 NUnit 版本有很多相同的问题。

Resharper NUnit 测试运行程序与 NUnit GUI 和命令行测试运行程序的代码库不同,由 Jetbrains 单独更新。

【讨论】:

    【解决方案3】:

    Peter 不幸的是,这似乎没有按预期工作。第一个测试 AsyncTaskCanceledSemiWorking 仅适用,因为我有 expectedexception 属性。实际的断言被完全忽略(正如您可以从 ArgumentOutOfRange 异常中看到的那样,这只是让它失败的假象)。

    期望是错误的,因为属性优先于 Assert.Throws。

    正如 Simone 所展示的,期望是“断言”异常是 InstanceOf,因此它属于 Assertion 而不是 throws。我认为您的期望可能是基于 Java 中 Throws 的使用方式。

    【讨论】:

    • 安德鲁,请不要干涉审核的名义,因为您完全误导了帖子。
    • 安德鲁,似乎被浏览器吃掉的 html 标签使用(“小于”和“大于”)让我认为它是由你编辑的。对较早的 cmets 感到抱歉(试图删除我的 cmets 但没有任何规定)。
    • 确实编辑了帖子。在我的编辑中,我肯定没有“误导”帖子;您错误地将 Peter 的名字放在 HTML 标记中。我为你解决了这个问题。不客气……
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-29
    • 2019-04-19
    • 1970-01-01
    • 2020-03-04
    • 2019-04-07
    相关资源
    最近更新 更多