2013-03-04 151 views
3

我想通過嘲笑依賴關係編寫單元測試用例。整體流程如下。如何編寫異步方法的單元測試用例?

我們有一個WorklistLoader它有一個異步方法LoadWorklistItemsAsync()。要完成這個任務WorklistLoader是依賴於低層API(我想模擬)QueryManager.StartQueryTask()StartQueryTask()也是一種異步方法,它查詢文件系統並定期提出ProgressChanged(),然後在最後引發CompletedEventStartQueryTask()返回對TPL​​ Task的引用。所述StartQueryTask

簽名是

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

一旦WorklistLoaderQueryManager臨危的ProgressChanged事件時,它會進行一些處理,然後提高其ProgressChanged事件(賦予了ViewModel已經訂閱)。

我想測試LoadWorklistItemsAsync()方法WorklistLoader與嘲諷QueryManager.StartQueryTask()

這是我的問題。

  1. 是編寫單元測試爲Async()方法與嘲諷的最佳做法?
  2. 如何編寫單元測試用例其相關使用TPL的方法呢?(方法返回Task型)

另一個問題是

  1. 如果我嘲笑使用Rhinomocks如何我QueryManager.StartQueryTask()方法它會是什麼樣子? (模擬代碼,它必須提高進度,完成事件並返回任務)。
+0

您使用的是模擬框架,或者只是手動實現一個接口/子類自己? – 2013-03-04 18:12:52

+0

問題1真的沒有什麼與異步,只是嘲笑。例如您必須以某種方式將模擬注入「WorklistLoader」,而不管使用模擬的最終方法是異步還是同步。至於2,我建議你看看http://www.srtsolutions.com/testing-async-methods-in-c-5 – 2013-03-04 19:39:25

回答

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-使用類的術語,你」我會注意到我已經實現了一個返回任務的異步測試方法。這告訴測試跑步者在考慮測試方法執行之前等待結果。如果你只是寫了一些東西,如:

[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(); 
} 
+0

我沒有使用C#5.0功能的豪華。我仍然明白,可以用更多的代碼來做同樣的事情。這導致了另一個問題。如果我嘲笑QueryManager使用Rhinomocks然後我應該怎麼做返回任務類型?我的模擬方法會如何? – 2013-03-05 16:14:53

+0

你必須告訴RhinoMocks返回什麼,例如'var theTask = CreateTask(); MockRepository.GenerateMock ()。存根(qm => StartQueryTask())。Return(theTask);' – 2013-03-05 16:24:13

+0

我爲非C#5添加了一些細節 – 2013-03-05 16:29:22

0

這似乎rinky,丁克,但我已經採取了類似的測試,建設場景單純的方法是使用這個方便的功能:

/// <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); 
相關問題