web-dev-qa-db-ja.com

async / awaitキーワードを使用しないと、非同期単体テストが失敗するのはなぜですか?

この議論 によると、次の2つの方法に違いはありません。

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

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

実際、非常に単純なメソッドの場合、オーバーヘッドがいくらか除去されるため、呼び出しwithout 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は無視され、次のメッセージが表示されます。

    テストメソッドの戻り値の型はvoidではありませんが、結果は期待できません

  • XUnitでは、Test2パス。

すべての場合にタスクがまだ待機しているので、NUnitおよびMSTestテストランナーに影響を与えるasyncキーワードについてはどうでしょうか。おそらくいくつかの反射の問題?

19

これらのテストランナーは、リフレクションを使用して、Taskを返すメソッドが本当に非同期メソッドであるかどうかを確認しているようです。これは、メソッドが実行された場合異なる動作をするという意味ではありません-しかし、実行されていないだけです。

それはそれを言っているようなものです:

public string Name { get; set; }

と同等です:

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

動作に関しては論理的には同じですが、リフレクションで十分に努力すれば、違いがわかります。この特定のケースでは、他にも微妙な違いがありますが、同じ一般原則が適用されます。

現在のNUnitコード(この記事の執筆時点)では、検出は AsyncInvocationRegion.cs

確かに、非同期メソッドを使用せずにTaskを返す単体テストを作成することは少なくとも珍しいですが、不可能にはほど遠いです。

8
Jon Skeet