わかりました-あなたの場合に当てはまるかもしれないし、そうでないかもしれないソリューションを開発する際にいくつかの仮定をしたので、私は以下があなたにとって助けになるかどうかわかりません。たぶん私の「解決策」はあまりにも理論的であり、人工的な例のためにのみ機能します-私は以下のもの以外のテストを行っていません。
さらに、実際の解決策よりも次の回避策がありますが、応答がないことを考えると、何もしないよりはましだと思います(解決策を待っているあなたの質問を見続けましたが、投稿された質問は見ていませんでした問題の周り)。
しかし、十分に言った:整数を取得するために使用できる単純なデータサービスがあるとします。
public interface IDataService
{
Task<int> LoadMagicInteger();
}
単純な実装では、非同期コードを使用します。
public sealed class CustomDataService
: IDataService
{
public async Task<int> LoadMagicInteger()
{
Console.WriteLine("LoadMagicInteger - 1");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 2");
var result = 42;
Console.WriteLine("LoadMagicInteger - 3");
await Task.Delay(100);
Console.WriteLine("LoadMagicInteger - 4");
return result;
}
}
ここで、このクラスで示されているように「誤って」コードを使用している場合、問題が発生します。Foo
次のように結果にアクセスするTask.Result
代わりに、誤ってアクセスします。await
Bar
public sealed class ClassToTest
{
private readonly IDataService _dataService;
public ClassToTest(IDataService dataService)
{
this._dataService = dataService;
}
public async Task<int> Foo()
{
var result = this._dataService.LoadMagicInteger().Result;
return result;
}
public async Task<int> Bar()
{
var result = await this._dataService.LoadMagicInteger();
return result;
}
}
私たち(あなた)が今必要としているのは、電話をかけると成功するが電話をかけるとBar
失敗するテストを書く方法ですFoo
(少なくとも質問を正しく理解していれば;-))。
コードに話させます。ここに私が思いついたものがあります(Visual Studioテストを使用しますが、NUnitを使用しても動作するはずです):
DataServiceMock
を利用しTaskCompletionSource<T>
ます。これにより、テスト実行の定義されたポイントで結果を設定でき、次のテストにつながります。TaskCompletionSourceをテストに戻すためにデリゲートを使用していることに注意してください。これをテストのInitializeメソッドに入れて、プロパティを使用することもできます。
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
ここで何が起こっているのかは、ブロックせずにメソッドを終了できることを最初に確認することです(誰かがアクセスした場合、これは機能しませんTask.Result
-この場合、メソッドが返されるまでタスクの結果が利用できないため、タイムアウトになります)。
次に、結果を設定し(メソッドを実行できるようになりました)、結果を検証します(単体テスト内で、実際にブロッキングを発生させたいときにTask.Resultにアクセスできます)。
完全なテストクラス- 必要に応じてBarTest
成功およびFooTest
失敗します。
[TestClass]
public class UnitTest1
{
private DataServiceMock _dataService;
private ClassToTest _instance;
private bool _end;
[TestInitialize]
public void Initialize()
{
this._dataService = new DataServiceMock();
this._instance = new ClassToTest(this._dataService);
this._end = false;
}
[TestCleanup]
public void Cleanup()
{
Assert.IsTrue(this._end);
}
[TestMethod]
public void FooTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
[TestMethod]
public void BarTest()
{
TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;
Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Bar());
tcs.TrySetResult(42);
var result = task.Result;
Assert.AreEqual(42, result);
this._end = true;
}
}
そして、デッドロック/タイムアウトをテストするための小さなヘルパークラス:
public static class TaskTestHelper
{
public static void AssertDoesNotBlock(Action action, int timeout = 1000)
{
var timeoutTask = Task.Delay(timeout);
var task = Task.Factory.StartNew(action);
Task.WaitAny(timeoutTask, task);
Assert.IsTrue(task.IsCompleted);
}
}
async
記事のコレクションを読んだことがありますか?