2014-02-24 26 views
3

我目前使用Moq來幫助我的單元測試,但是我遇到了一個我不知道如何解決的問題。單元測試ThrowIfCancellationRequested()被稱爲

例如,假設我想驗證CancellationToken.ThrowIfCancellationRequested()被調用一次每Upload(通話

public UploadEngine(IUploader uploader) 
{ 
    _uploader = uploader; 
} 

public void PerformUpload(CancellationToken token) 
{ 
    token.ThrowIfCancellationRequested(); 
    _uploader.Upload(token, "Foo"); 

    token.ThrowIfCancellationRequested(); 
    _uploader.Upload(token, "Bar"); 
} 

如果token是引用類型,我通常會做類似

[TestMethod()] 
public void PerformUploadTest() 
{ 
    var uploader = new Mock<IUploader>(); 
    var token = new Mock<CancellationToken>(); 

    int callCount = 0; 

    uploader.Setup(a => a.Upload(token.Object, It.IsAny<string>())).Callback(() => callCount++); 
    token.Setup(a => a.ThrowIfCancellationRequested()); 

    var engine = new UploadEngine(uploader.Object); 
    engine.PerformUpload(token.Object); 

    token.Verify(a => a.ThrowIfCancellationRequested(), Times.Exactly(callCount)); 
} 
從什麼

然而我可以告訴Moq不支持值類型。什麼是正確的方法來測試這個,或者是沒有辦法做到我想通過Moq而不需要在容器中裝入CancellationToken首先被傳遞到PerformUpload(

+1

爲什麼不創建一個CancellationTokenSource和將其.Token屬性傳遞給上傳器並使用ExpectedException屬性裝飾測試? –

+0

因爲我沒有測試,如果取消完成,它會被捕獲。我正在測試模塊調用與取消檢查的比率是1:1。 –

+0

我明白了。我不確定你會嘲笑throwIf,因爲它不是虛擬的。您可能需要將令牌包裝在您自己的界面中並對其進行模擬。 –

回答

2

你可能已經開始從這裏開始,但是我想到你想要測試的東西似乎並不合理。測試ThrowIfCancellationRequested的調用次數與Upload的調用次數沒有任何關係,以確保它們以正確的順序被調用,我認爲這種調用在這種情況下實際上是相關的。你不希望這樣的代碼通過,但我敢肯定它會:

_uploader.Upload(token, "Foo"); 

token.ThrowIfCancellationRequested(); 
token.ThrowIfCancellationRequested(); 

_uploader.Upload(token, "Bar"); 

正如在評論中有人說,要避開這個問題最簡單的方法將是推token.ThrowIfCancellationRequested呼叫進入致電Upload。假設無論出於何種原因這是不可能的,我可能會採取以下方法來測試您的方案。

首先,我將封裝檢查功能,以查看是否已請求取消,如果沒有,請將某個操作調用爲可測試的操作。起初認爲,這可能看起來像這樣:

public interface IActionRunner { 
    void ExecIfNotCancelled(CancellationToken token, Action action); 
} 

public class ActionRunner : IActionRunner{ 
    public void ExecIfNotCancelled(CancellationToken token, Action action) { 
     token.ThrowIfCancellationRequested(); 
     action(); 
    } 
} 

這可以用兩個測試進行相當簡單的測試。一個用於檢查如果令牌未被取消而被調用的動作,另一個用於驗證該動作是否被取消。這些測試將如下所示:

[TestMethod] 
public void TestActionRunnerExecutesAction() { 
    bool run = false; 
    var runner = new ActionRunner(); 
    var token = new CancellationToken(); 

    runner.ExecIfNotCancelled(token,() => run = true); 

    // Validate action has been executed 
    Assert.AreEqual(true, run); 
} 

[TestMethod] 
public void TestActionRunnerDoesNotExecuteIfCancelled() { 
    bool run = false; 
    var runner = new ActionRunner(); 
    var token = new CancellationToken(true); 

    try { 
     runner.ExecIfNotCancelled(token,() => run = true); 
     Assert.Fail("Exception not thrown"); 
    } 
    catch (OperationCanceledException) { 
     // Swallow only the expected exception 
    } 
    // Validate action hasn't been executed 
    Assert.AreEqual(false, run); 
} 

我會再注入IActionRunnerUploadEngine,並驗證它是正確調用。所以,你的PerformUpload方法將改變爲:

public void PerformUpload(CancellationToken token) { 
    _actionRunner.ExecIfNotCancelled(token,() => _uploader.Upload(token, "Foo")); 
    _actionRunner.ExecIfNotCancelled(token,() => _uploader.Upload(token, "Bar")); 
} 

然後,您可以寫一對測試,以驗證PerformUpload。第一個檢查是否已經設置ActionRunner模擬執行提供的動作,然後Upload至少調用一次。第二個測試驗證如果ActionRunner模擬已設置爲忽略該操作,則不調用Upload。這基本上確保在該方法中的呼叫通過ActionRunner完成。這些測試是這樣的:

[TestMethod] 
public void TestUploadCallsMadeThroughActionRunner() { 
    var uploader = new Mock<IUploader>(); 
    var runner = new Mock<IActionRunner>(); 
    var token = new CancellationToken(); 

    int callCount = 0; 

    uploader.Setup(a => a.Upload(token, It.IsAny<string>())).Callback(() => callCount++); 
    // Use callback to invoke actions supplied to runner 
    runner.Setup(x => x.ExecIfNotCancelled(token, It.IsAny<Action>())) 
      .Callback<CancellationToken, Action>((tok,act)=>act()); 

    var engine = new UploadEngine(uploader.Object, runner.Object); 
    engine.PerformUpload(token); 

    Assert.IsTrue(callCount > 0); 
} 

[TestMethod] 
public void TestNoUploadCallsMadeThroughWithoutActionRunner() { 
    var uploader = new Mock<IUploader>(); 
    var runner = new Mock<IActionRunner>(); 
    var token = new CancellationToken(); 

    int callCount = 0; 

    uploader.Setup(a => a.Upload(token, It.IsAny<string>())).Callback(() => callCount++); 
    // NOP callback on runner prevents uploader action being run 
    runner.Setup(x => x.ExecIfNotCancelled(token, It.IsAny<Action>())) 
      .Callback<CancellationToken, Action>((tok, act) => { }); 

    var engine = new UploadEngine(uploader.Object, runner.Object); 
    engine.PerformUpload(token); 

    Assert.AreEqual(0, callCount); 
} 

有顯然是,你可能想要寫您的UploadEngine其他測試,但他們似乎超出範圍爲當前的問題...