5

私は本当にそれを待つことなく、Task.Delayを待っているユニットテストコンポーネントをどのようにすることができます。例えばTask.Delayを含む単体テストコードはどのようにして単体テストできますか?

public void Retry() 
{ 
    // doSomething(); 
    if(fail) 
     await Task.Delay(5000); 
} 

は私が失敗枝をテストする必要がありますが、私は私のテストは、5秒間待つ必要はありません。

rx virtual time-based schedulingはタスク並列ライブラリで利用できますか?

答えて

6

タイマーは外部依存関係の単なる別の形式です。唯一の違いは、データベースではなく時計に依存することです。他の外部依存と同様に、テストを難しくします。その答えは、テストが容易になるまでデザインを改善することです。

依存性注入の原則に従うと、オブジェクトのコンストラクタにタイマーを渡す必要があります。これにより、実際のタイマーに頼るのではなくテストスタブを注入できます。

これがデザインを改善する方法に注意してください。多くの場合、タイムアウト期間は誰が呼び出しているかによって異なります。これにより、タイムアウト期間を設定する責任がアプリケーションの待機層に移ります。それがどれくらい待っているかを理解しなければならない層です。

1

あなたのテストでタイムアウトを追加し、それを待たなければならない場合は、失敗することを考慮することができます。
タイムアウトを渡すことも、タイムアウトをテスト用に別の方法で構成することもできます。

、非同期およびTDDのblogと別の1 hereありspecifcally Task.Delayを扱うのではなく、非同期コードで一般的に間違って行くことができるか、これらのよりポイントアウトけれども。

4

タスク並列ライブラリで使用できるrx仮想時間ベーススケジューリングはありますか?

いいえ、ありません。あなたのオプションは、テストスタブで実装できる "タイマーサービス"を定義するか、Microsoft Fakesを使用してTask.Delayへの呼び出しを代行受信することです。私は後者を好むが、それはVS Ultimateのオプションに過ぎない。

+0

あなたは、後者のオプションがどのように実行できるかについていくつか詳しく説明していますか? –

+0

私はまだブログ記事を書いていませんが、[コードはこちらです](https://github.com/StephenCleary/Presentations/tree/ba85a29179091e86743ba0f45c01a1d93fb43f4b/Async%20Unit%20Testing/Demos/Timing) –

+0

どのようにしましたか?偽を作成できるように、mscorlib.dllへの参照を追加しますか? – Choco

2

1)Task.Delayの独自の実装を定義します。

public static class TaskEx 
{ 
    private static bool _shouldSkipDelays; 

    public static Task Delay(TimeSpan delay) 
    { 
     return _shouldSkipDelays ? Task.FromResult(0) : Task.Delay(delay); 
    } 

    public static IDisposable SkipDelays() 
    { 
     return new SkipDelaysHandle(); 
    } 

    private class SkipDelaysHandle : IDisposable 
    { 
     private readonly bool _previousState; 

     public SkipDelaysHandle() 
     { 
      _previousState = _shouldSkipDelays; 
      _shouldSkipDelays = true; 
     } 

     public void Dispose() 
     { 
      _shouldSkipDelays = _previousState; 
     } 
    } 
} 

2)使用TaskEx.Delayの代わりに、どこでも、あなたのコードでTask.Delay。あなたのテストでは

3)TaskEx.SkipDelaysを使用します。Task.Delayのラッパーを作成するために、アレクセイのanwserに基づいて

[Test] 
public async Task MyTest() 
{ 
    using (TaskEx.SkipDelays()) 
    { 
     // your code that will ignore delays 
    } 
} 
+0

私はこれが最良の選択だと思っていますが、より良い実装が可能です。 Rxを使用する場合は、IScheduler.Schedule(Timespan)を使用してこのクラスを実装することもできます。これにより、Rxテストスケジューラを使用して、必要に応じてタイムアウトを実際にテストすることができます。その実装は基本的に同じです。 – Natan

0

を、ここでのタスクを作成する方法です。あなたが遅延をテストするために仮想時間を使うことができるように、反応性ExtensionsのISchedulerを使用していますディレイ:

using System; 
using System.Reactive.Linq; 
using System.Reactive.Threading.Tasks; 
using System.Threading; 
using System.Threading.Tasks; 

public static class TaskEx 
{ 
    public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken = default(CancellationToken)) 
    { 
     #if TEST 
     return Observable.Timer(TimeSpan.FromMilliseconds(millisecondsDelay), AppContext.DefaultScheduler).ToTask(cancellationToken); 
     #else 
     return Task.Delay(millisecondsDelay, cancellationToken); 
     #endif 
    } 
} 

これは、ユニットテストされていない場合、完全にRxのを避けるために、コンパイルシンボルを使用しています。

AppContextはスケジューラへの参照を持つコンテキストオブジェクトです。あなたのテストでは、AppContext.DefaultScheduler = testSchedulerを設定することができ、遅延は仮想タイムスケジューラによって引き起こされます。

ただし、注意が必要です。 TestSchedulerは同期式なので、タスクがスケジュールされる前にスケジューラが前進するので、タスクを開始してTaskEx.Delayを使用することはできません。

var scheduler = new TestScheduler(); 
AppContext.DefaultScheduler = scheduler; 

Task.Run(async() => { 
    await TaskEx.Delay(100); 
    Console.Write("Done"); 
}); 

/// this won't work, Task.Delay didn't run yet. 
scheduler.AdvanceBy(1); 

代わりに、あなたは常にObservable.Start(task, scheduler)を使用してタスクを開始する必要があり、そのタスクが順番に実行されます。

var scheduler = new TestScheduler(); 
AppContext.DefaultScheduler = scheduler; 

Observable.Start(async() => { 
    await TaskEx.Delay(100); 
    Console.Write("Done"); 
}, scheduler); 

/// this runs the code to schedule de delay 
scheduler.AdvanceBy(1); 

/// this actually runs until the delay is complete 
scheduler.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks); 

これは確かに複雑ですので、私はどこでも私は、タスクを使用して、それを使用することはありません。ディレイ。しかし、遅延がアプリケーションの動作を変更し、あなたがそれをテストする必要があるコードのいくつかの特定の部分があるので、これらの特別な場合に便利です。

0

John Detersによると、これはコンピュータの時計に対する外部依存関係です(現在の時刻を取得する必要があるように、DateTime.Utcを呼び出すのは簡単ですが依存関係です)。

ただし、これらは特別な依存関係で、常に動作するデフォルト(Task.DelayまたはDateTime.UtcNow)を提供できます。これを使用する

private Func<int, Task> delayer = millisecondsDelay => Task.Delay(millisecondsDelay); 
    public Func<int, Task> Delayer 
    { 
     get { return delayer; } 
     set { delayer = value ?? (millisecondsDelay => Task.Delay(millisecondsDelay)) } 
    } 

、あなたのテストでTask.Delayへの呼び出しを置き換えることができます:あなたはそれ故にこれを行うためのプロパティを持つことができます。

sut.Delayer = _ => Task.CompletedTask; 

これを行うには、もちろんインターフェイスを宣言し、コンストラクタ経由で取得するのが賢明です。

関連する問題