2010-06-08 78 views
8

我正在爲我的應用程序的「膠合」層編寫單元測試,並且難以爲允許用戶過早取消操作的異步方法創建確定性測試。在長時間運行的單元測試中協調取消

具體來說,在一些異步方法中,我們有代碼對取消調用做出反應,並確保對象在完成之前處於適當狀態。我想確保這些代碼路徑被測試覆蓋。

某些C#僞代碼在此方案中例示的典型異步方法如下:

public void FooAsync(CancellationToken token, Action<FooCompletedEventArgs> callback) 
{ 
    if (token.IsCancellationRequested) DoSomeCleanup0(); 

    // Call the four helper methods, checking for cancellations in between each 
    Exception encounteredException; 
    try 
    { 
     MyDependency.DoExpensiveStuff1(); 
     if (token.IsCancellationRequested) DoSomeCleanup1(); 

     MyDependency.DoExpensiveStuff2(); 
     if (token.IsCancellationRequested) DoSomeCleanup2(); 

     MyDependency.DoExpensiveStuff3(); 
     if (token.IsCancellationRequested) DoSomeCleanup3(); 

     MyDependency.DoExpensiveStuff4(); 
     if (token.IsCancellationRequested) DoSomeCleanup4(); 

    } 
    catch (Exception e) 
    { 
     encounteredException = e; 
    } 

    if (!token.IsCancellationRequested) 
    { 
     var args = new FooCompletedEventArgs(a bunch of params); 
     callback(args); 
    } 
} 

,我已想出迄今涉及嘲笑底層MyDependency操作由膠水層包裹的溶液,並迫使每個人在任意一段時間內入睡。然後我調用異步方法,並告訴我的單元測試在取消異步請求之前休眠數毫秒。

像這樣(使用犀牛製品作爲示例):

[TestMethod] 
public void FooAsyncTest_CancelAfter2() 
{ 
    // arrange 
    var myDependency = MockRepository.GenerateStub<IMyDependency>(); 

    // Set these stubs up to take a little bit of time each so we can orcestrate the cancels 
    myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100)); 
    myDependency.Stub(x => x.DoExpensiveStuff2()).WhenCalled(x => Thread.Sleep(100)); 
    myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => Thread.Sleep(100)); 
    myDependency.Stub(x => x.DoExpensiveStuff4()).WhenCalled(x => Thread.Sleep(100)); 

    // act 
    var target = new FooClass(myDependency); 

    CancellationTokenSource cts = new CancellationTokenSource(); 
    bool wasCancelled = false; 

    target.FooAsync(
     cts.Token, 
     args => 
     { 
     wasCancelled = args.IsCancelled; 
     // Some other code to manipulate FooCompletedEventArgs 
     }); 

    // sleep long enough for two operations to complete, then cancel 
    Thread.Sleep(250); 
    cts.Cancel(); 

    // Some code to ensure the async call completes goes here 

    //assert 
    Assert.IsTrue(wasCancelled); 
    // Other assertions to validate state of target go here 
} 
從在單元測試使用了Thread.Sleep讓我反胃的事實

除了,更大的問題是,有時這樣的測試失敗在我們的構建服務器上,如果它恰好處於顯着負載下。異步呼叫太過分了,取消來得太遲。

任何人都可以提供一個更可靠的單元測試取消邏輯的長期運行操作像這樣嗎?任何想法,將不勝感激。

回答

5

我會嘗試使用mock以同步方式「模擬」異步行爲。除了使用

myDependency.Stub(x => x.DoExpensiveStuff1()).WhenCalled(x => Thread.Sleep(100)); 

,然後毫秒的任何數字內設置取消標誌,我只想把它作爲回調的一部分:

myDependency.Stub(x => x.DoExpensiveStuff1()); 
myDependency.Stub(x => x.DoExpensiveStuff2()); 
myDependency.Stub(x => x.DoExpensiveStuff3()).WhenCalled(x => cts.Cancel()); 
myDependency.Stub(x => x.DoExpensiveStuff4()); 

從代碼的角度來看,這看起來好像通話過程中發生取消。

+0

這看起來非常有前途。我今天下午試一試,然後再報告。 – 2010-06-08 15:55:16

+0

它工作得很漂亮。謝謝你的幫助! – 2010-06-08 19:45:35

1

每個長時間運行的操作應該在事件開始運行時觸發。

在單元測試中勾住此事件。這給出了具有確定性的結果,這些事件在將來可能有用。