3

依存関係をモックして単体テストケースを書きたいと思います。全体の流れは以下の通りです。

WorklistLoaderasync method を持つ がありますLoadWorklistItemsAsync()。このタスクを達成するWorklistLoaderには、下位層の API (モックしたい) に依存していますQueryManager.StartQueryTask()StartQueryTask()は、ファイル システムにクエリを実行し、ProgressChanged()定期的に を発生させ、最後に. を発生させる非同期メソッドでもありますCompletedEventStartQueryTask()TPL への参照を返しますTask

StartQueryTaskの署名

Task StartQueryTask(
    "SomeId",
    EventHandler<ProgressChanged> progressChanged,
    EventHandler<QueryCompleted> queryCompleted);

がからイベントをWorklistLoader受信すると、何らかの処理を行ってProgressChangedからQueryManager、そのProgressChangedイベント (ViewModelがサブスクライブしたもの) を発生させます。

LoadWorklistItemsAsync()のメソッドWorklistLoaderをモック でテストしたいと思いQueryManager.StartQueryTask()ます。

これが私の質問です。

  1. Async()モッキングを使用してメソッドの単体テストを作成するためのベスト プラクティスは何ですか?
  2. 依存関係が TPL を使用するメソッドの単体テスト ケースの記述方法 (Task型を返すメソッド)

もう一つの質問は

  1. Rhinomocks を使用して QueryManager.StartQueryTask() メソッドをモックすると、どのようになりますか? (モックコード。progresschanged、completed イベントを発生させ、Task を返す必要があります)。
4

2 に答える 2

1

何かをモックするには、使用しているものにモックを注入できる必要があります。Inversion of Control コンテナー、アンビエント コンテキスト ブートストラップ コードなどを使用して、これを行う方法はたくさんあります。最も簡単な方法は、コンストラクター インジェクションとアンビエント コンテキストをブートストラップして、テストしたいときに必要なモックを作成することです。例えば:

WorklistLoader worklistLoader;

[SetUp]
public void Setup()
{
    worklistLoader = new WorklistLoader(new MockQueryManager());
}

[Test]
public async Task TestWorklistLoader()
{
    await worklistLoader.LoadWorklistItemsAsync();
}

これは、WorklistLoader が依存するのではなく、実装されるような抽象化に依存することも意味しQueryManagerます。IQueryManagerMockQueryManager

MockQueryManager次のような場所があります。

public class MockQueryManager : IQueryManager
{
    public Task StartQueryTask() {/* TODO: */}
}

もちろん、元の QueryManager は IQueryManagear を実装する必要があります。

public class QueryManager : IQueryManager
{
    public Task StartQueryTask() {/* TODO: */}
}

ここで、TPL を使用するクラスのテストに関して言えば、Task を返す非同期テスト メソッドを実装したことに気付くでしょう。これにより、テスト ランナーは、テスト メソッドが実行されたと考える前に結果を待つように指示されます。単に次のようなものを書いた場合:

[Test]
public async void TestWorklistLoader()
{
    await worklistLoader.LoadWorklistItemsAsync();
}

ランナーは実行され、完了するTestWorklistLoader直前に戻りLoadWorklistItemsAsync、おそらくアサートをバイパスします。

アップデート:

C# 5 を使用していない場合は、単体テスト内でタスクが完了するのを待つことをお勧めします。例えば:

[Test]
public void TestWorklistLoader()
{
    var task = worklistLoader.LoadWorklistItemsAsync();
    if(!task.IsComplete()) task.Wait();
}
于 2013-03-04T19:45:22.600 に答える
0

これはばかげているように思えるかもしれませんが、同様のテスト構築シナリオに対して私が取った単純なアプローチは、次の便利な関数を使用することです。

/// <summary>
/// Wait no longer than @waitNoLongerThanMillis for @thatWhatWeAreWaitingFor to return true.
/// Tests every second for the 
/// </summary>
/// <param name="thatWhatWeAreWaitingFor">Function that when evaluated returns true if the state we are waiting for has been reached.</param>
/// <param name="waitNoLongerThanMillis">Max time to wait in milliseconds</param>
/// <param name="checkEveryMillis">How often to check for @thatWhatWeAreWaitingFor</param>
/// <returns></returns>
private bool WaitFor(Func<bool> thatWhatWeAreWaitingFor, int checkEveryMillis, int waitNoLongerThanMillis)
{
    var waitedFor = 0;
    while (waitedFor < waitNoLongerThanMillis)
    {
        if (thatWhatWeAreWaitingFor()) return true;

        Console.WriteLine("Waiting another {0}ms for a situation to occur.  Giving up in {1}ms ...", checkEveryMillis, (waitNoLongerThanMillis - waitedFor));
        Thread.Sleep(checkEveryMillis);
        waitedFor += checkEveryMillis;
    }
    return false;
}

使用法:

// WaitFor (transaction to be written to file, checkEverySoOften, waitNoLongerThan)
int wait = (Settings.EventHandlerCoordinatorNoActivitySleepTime + 5) * 1000;
var fileExists = WaitFor(() => File.Exists(handlerConfig["outputPath"]), checkEveryMillis: 1000, waitNoLongerThanMillis: wait);

if(!fileExists)
     Assert.Fail("Waited longer than " + wait + " without any evidence of the event having been handled.  Expected to see a file appear at " + handlerConfig["outputPath"]);

私のシナリオでは、ファイルが書き込まれることを期待しているので、それを待っています。あなたの場合、progressChanged と queryCompleted が呼び出されるのを待っているので、それらのモックを注入することをお勧めします。真になるのを待っている式は次のとおりです。

var eventsCalled = WaitFor(() => progressChanged.Called(Time.Once) && queryCompleted.Called(Times.Once), checkEveryMillis: 1000, waitNoLongerThanMillis: wait);
于 2013-03-12T21:23:50.620 に答える