您可能已经从这里开始,但我突然想到您尝试测试的内容似乎没有任何意义。测试ThrowIfCancellationRequested 的调用次数与Upload 的调用次数相同,并不能确保它们以正确的顺序被调用,我假设这在这种情况下实际上是相关的。你不希望这样的代码通过,但我很确定它会:
_uploader.Upload(token, "Foo");
token.ThrowIfCancellationRequested();
token.ThrowIfCancellationRequested();
_uploader.Upload(token, "Bar");
正如 cmets 中所说,解决此问题的最简单方法是将 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。这基本上确保了方法中的所有 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 编写其他测试,但它们似乎超出了当前问题的范围...