2011-12-02 147 views
4

我有下面的代碼,我試圖用NUnit和Rhino mocks來測試。如何測試涉及SynchronizationContext的代碼?

void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e) 
{ 
    // _context is initialised as 
    // SynchronizationContext _context = SynchronizationContext.Current; 
    // _tracker.Index is stubbed to return the value 100 
    _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null); 
} 

在測試用例我已經設置了期望爲

_view.Expect(v => v.SetTrackbarValue(100)); 

,當我確認期望,單元測試與消息randomaly失敗

Test(s) failed. Rhino.Mocks.Exceptions.ExpectationViolationException : 
IIndexTrackerView.SetTrackbarValue(100); Expected #1, Actual #0. 

我沒有在這裏找到問題,如何我是否修復它?

回答

6

我通常通過使用抽象類或接口封裝全局狀態來解決像這樣的問題,抽象類或接口可以被實例化和嘲弄。然後,我不是直接訪問全局狀態,而是將我的抽象類或接口的實例注入到使用它的代碼中。

這讓我嘲笑全球的行爲,並使其成爲我的測試不依賴於或行使不相關的行爲。

以下是您可以這樣做的一種方法。

public interface IContext 
{ 
    void Post(SendOrPostCallback d, Object state); 
} 

public class SynchronizationContextAdapter : IContext 
{ 
    private SynchronizationContext _context; 

    public SynchronizationContextAdapter(SynchronizationContext context) 
    { 
     _context = context; 
    } 

    public virtual void Post(SendOrPostCallback d, Object state) 
    { 
     _context.Post(d, state); 
    } 
} 

public class SomeClass 
{ 
    public SomeClass(IContext context) 
    { 
     _context = context; 
    } 

    void _tracker_IndexChanged(object sender, IndexTrackerChangedEventArgs e) 
    { 
     _context.Post(o => _view.SetTrackbarValue(_tracker.Index), null); 
    } 
    // ... 
} 

然後你可以嘲笑或存根出IContext,所以你不必擔心線程,並且可以使用一個簡單的實現,只是執行的委託。如果我編寫了單元測試來嘲笑這一點,我還會編寫更高級別的「集成」測試,但是它沒有模擬出它,但是沒有更細化的驗證。

+0

+1,但確實要確保調用了_view.SetTrackbarValue! –

+2

@Prashant:那麼我肯定會把「上下文」類剔除,而不是嘲笑它。讓它直接調用傳遞給它的委託。在你的單元測試中,在視圖上而不是在上下文類中設置你的期望值。將此測試與幾個簡單的基於UI的集成測試相結合,以確保在替換真正的SynchronizationContext實現時不會出現任何奇怪的錯誤。 –

1

我有一段時間沒有使用Rhino Mocks,所以我不記得確切的語法,但如果Merlyn建議的重構不是一個選項,那麼另一種解決方案是使用ManualResetEvent等待,直到您的模擬已經採取行動。

事情是這樣的:

[Test] 
public void ATest(){ 
    ManualResetEvent completed = new ManualResetEvent(false); 

    _view.Expect(v => v.SetTrackbarValue(100)).Do(() => completed.Set()); 
    //Stuff done here... 
    Assert.IsTrue(completed.WaitOne(1000), "Waited a second for a call that never arrived!"); 

} 

這樣,你可以等待,直到其他線程觸發事件,此時你可以繼續。確保有一個明智的超時,所以你不要永遠等待!

+0

爲什麼你認爲這將是更新視圖的另一個線程?我認爲這將是'WaitOne'語句中停止的同一個線程。 – Snowbear

+0

@Snowbear - 如果SynchronisationContext上的線程調用帖子是運行測試的線程,那麼這種方法顯然無法工作。我正在使用一個SynchronisationContext來做一個假設,它正在一個單獨的線程上運行。 –