Sco*_*ain 5 c# unit-testing mstest moq mocking
我目前正在使用Moq来帮助进行单元测试,但是遇到了一个我不知道如何解决的问题。
例如,假设我想验证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));
}
但是据我所知,最小起订量不支持值类型。什么是测试此方法的正确方法,或者没有在不CancellationToken首先将内部装箱的容器装箱之前通过Moq做我想做的事情PerformUpload(?
您可能已经从这里开始,但我突然想到您尝试测试的内容似乎没有任何意义。测试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不会调用。这基本上确保了方法中的所有 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但它们似乎超出了当前问题的范围……
| 归档时间: | 
 | 
| 查看次数: | 2444 次 | 
| 最近记录: |