8

多数の URL (非同期) を呼び出してコンテンツを取得する、作成中のクラスを単体テストしようとしています。

これが私が問題を抱えているテストです:

[Test]
public void downloads_content_for_each_url()
{
    _mockGetContentUrls.Setup(x => x.GetAll())
        .Returns(new[] { "http://www.url1.com", "http://www.url2.com" });

    _mockDownloadContent.Setup(x => x.DownloadContentFromUrlAsync(It.IsAny<string>()))
        .Returns(new Task<IEnumerable<MobileContent>>(() => new List<MobileContent>()));

    var downloadAndStoreContent= new DownloadAndStoreContent(
        _mockGetContentUrls.Object, _mockDownloadContent.Object);

    downloadAndStoreContent.DownloadAndStore();

    _mockDownloadContent.Verify(x => x.DownloadContentFromUrlAsync("http://www.url1.com"));
    _mockDownloadContent.Verify(x => x.DownloadContentFromUrlAsync("http://www.url2.com"));
}

の関連部分は次のDownloadContentとおりです。

    public void DownloadAndStore()
    {
        //service passed in through ctor
        var urls = _getContentUrls.GetAll();

        var content = DownloadAll(urls)
            .Result;

        //do stuff with content here
    }

    private async Task<IEnumerable<MobileContent>> DownloadAll(IEnumerable<string> urls)
    {
        var list = new List<MobileContent>();

        foreach (var url in urls)
        {
            var content = await _downloadMobileContent.DownloadContentFromUrlAsync(url);
            list.AddRange(content);
        }

        return list;
    }

テストを実行すると、完了せず、ハングするだけです。

私のセットアップの何かが原因だと思います_mockDownloadContent...

4

2 に答える 2

8

awaitを使用して非同期メソッドを起動しているときに、古典的なデッドロックの問題が発生しawait、起動後すぐにそのタスクでブロッキング待機を行っています ( を呼び出しResultたときDownloadAndStore)。

呼び出すawaitと、値がキャプチャさSynchronizationContext.Currentれ、呼び出しから生じるすべての継続awaitがその同期コンテキストにポストされることが保証されます。

つまり、タスクを開始していて、非同期操作を実行しています。その継続に進むには、その継続を処理できるように、ある時点で同期コンテキストを「解放」する必要があります。

次に、タスクを待機している (同じ同期コンテキスト内の) 呼び出し元からのコードがあります。タスクが完了するまでその同期コンテキストを保持することをあきらめませんが、タスクを完了するには同期コンテキストを解放する必要があります。これで、互いに待機している 2 つのタスクができました。古典的なデッドロック。

ここにはいくつかのオプションがあります。理想的な解決策の 1 つは、「完全に非同期」にして、最初から同期コンテキストを決してブロックしないことです。これには、テスト フレームワークからのサポートが必要になる可能性が高くなります。

別のオプションは、await呼び出しが同期コンテキストにポストバックされないようにすることです。ConfigureAwait(false)これは、必要なすべてのタスクに追加することで実行できますawait。これを行う場合は、テスト フレームワークだけでなく、実際のプログラムでも必要な動作であることを確認する必要があります。実際のフレームワークでキャプチャ同期コンテキストを使用する必要がある場合、それはオプションではありません。

また、各テストのスコープ内で使用する独自の同期コンテキストを使用して、独自のメッセージ ポンプを作成することもできます。これにより、すべての非同期操作が完了するまでテスト自体をブロックできますが、そのメッセージ ポンプ内のすべてを完全に非同期にすることができます。

于 2013-10-14T17:17:27.410 に答える