你可能已經開始從這裏開始,但是我想到你想要測試的東西似乎並不合理。測試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);
}
我會再注入IActionRunner
到UploadEngine
,並驗證它是正確調用。所以,你的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
其他測試,但他們似乎超出範圍爲當前的問題...
爲什麼不創建一個CancellationTokenSource和將其.Token屬性傳遞給上傳器並使用ExpectedException屬性裝飾測試? –
因爲我沒有測試,如果取消完成,它會被捕獲。我正在測試模塊調用與取消檢查的比率是1:1。 –
我明白了。我不確定你會嘲笑throwIf,因爲它不是虛擬的。您可能需要將令牌包裝在您自己的界面中並對其進行模擬。 –