异步单元测试未按预期工作

时间:2012-10-24 19:56:04

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

我使用resharper和visual studio test runner在Visual Studio 2012中使用最新版本的NUnit(2.6.2)。我有以下示例测试,其中我试图验证在预期的异步方法调用上引发异常。

不幸的是,这似乎没有像预期的那样发挥作用。第一个测试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;
    } 

3 个答案:

答案 0 :(得分:10)

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

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

await LongRunningFunction(token)

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

throw new TaskCanceledException()

因此,为什么前两个测试成功 - 你正在使用ExpectedExceptionAttribute - 第三个失败 - 你不期待异常。

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

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

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

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

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

我可以给你的另一个选择是使用非同步lambda,你{as}操作返回的任务Wait,但遗憾的是语法不太好,因为等待异步操作的行为与等待返回的任务的行为方式不同。具体来说,在异步操作抛出异常的情况下,您将在第一种情况下获得实际异常,在第二种情况下获得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<T>]测试的异步测试方法的支持,但我们没有考虑将支持扩展到异步匿名方法,这些方法对此有用。虽然我相信我们可以做出这样的断言。

答案 1 :(得分:0)

如果将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单独更新。

答案 2 :(得分:-2)

Peter Unfortuantely,这似乎没有按预期工作。第一个测试AsyncTaskCanceledSemiWorking只能工作,因为我有expectedexception属性。实际的断言完全被忽略(正如您可以通过ArgumentOutOfRange异常看到的那样,这只是假的失败)。

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

正如Simone已经证明的那样,期望是“断言”异常是InstanceOf,因此,它属于Assertion而不是抛出。我认为您的期望可能基于在Java中使用Throws的方式。