如果不使用async/await关键字,为什么异步单元测试会失败?

20

根据这个讨论,以下两种方法应该没有区别:

public async Task Foo()
{
    await DoSomethingAsync();
}

public Task Foo()
{
    return DoSomethingAsync();
}

实际上,对于非常简单的方法,使用没有async/await关键字的调用似乎更受欢迎,因为它们消除了一些开销。

然而,在单元测试中,这显然并不总是奏效。

MSTest

[TestClass]
public class AsyncTest
{
    [TestMethod]
    public async Task Test1()
    {
        await Task.Delay(0);
    }

    [TestMethod]
    public Task Test2()
    {
        return Task.Delay(0);
    }
}

NUnit

[TestFixture]
public class AsyncTest
{
    [Test]
    public async Task Test1()
    {
        await Task.Delay(0);
    }

    [Test]
    public Task Test2()
    {
        return Task.Delay(0);
    }
}

XUnit

public class AsyncTest
{
    [Fact]
    public async Task Test1()
    {
        await Task.Delay(0);
    }

    [Fact]
    public Task Test2()
    {
        return Task.Delay(0);
    }
}
  • 在所有的情况下,Test1 通过。
  • 在 MSTest 中,Test2 在测试运行器中显示,但不运行。
  • 在 NUnit 中,Test2 被忽略,并显示以下消息:

    Test method has non-void return type, but no result is expected

  • 在 XUnit 中,Test2 通过。

由于在所有情况下任务仍然可以等待,那么 async 关键字如何影响 NUnit 和 MSTest 测试运行器?也许是某种反射问题吗?


1
实际上,Test1Test2之间是有一定的区别的 :) - noseratio - open to work
1
@Noseratio - 谢谢! - Matt Johnson-Pint
1个回答

9

听起来这些测试运行器可能使用反射来检查返回Task的方法是否真正是异步方法。这并不意味着如果运行它们,该方法会有不同的行为 - 但它们只是没有运行。

这就像说:

public string Name { get; set; }

等同于:

private string name;
public Name { get { return name; } set { name = value; } }

在行为上它们是逻辑相同的,但如果你尝试使用反射,你可以分辨出它们之间的区别。在这种特定情况下,还有其他更微妙的差异,但是同样的基本原则适用。

从目前的 NUnit 代码(截至本篇文章撰写时)来看,检测是在AsyncInvocationRegion.cs中进行的。

诚然,编写一个返回Task但没有使用异步方法的单元测试至少是不寻常的,但远非不可能。


1
我明白了。因此,NUnit特别期望编译器已经使用AsyncStateMachineAttribute装饰了该方法。我猜这就是解释的原因。MSTest可能在做类似的事情。 - Matt Johnson-Pint
2
@MattJohnson:当然,你可以自己添加那个属性,我猜只是为了欺骗它。也许值得试一试,看看MSTest会做什么... - Jon Skeet
2
是的。添加了[AsyncStateMachine(typeof(Task))],现在可以在MSTest和NUnit下通过测试。虽然我认为我会继续使用async/await关键字。 :) - Matt Johnson-Pint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接