gpt4 book ai didi

c# - 调用了单元测试 ThrowIfCancellationRequested()

转载 作者:太空狗 更新时间:2023-10-30 01:36:31 25 4
gpt4 key购买 nike

我目前正在使用 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));
}

但是据我所知,Moq 不支持值类型。测试这个的正确方法是什么,或者如果不先将 CancellationToken 装箱到容器中以传递给 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();
}
}

这可以通过两个测试进行相当简单的测试。一种用于检查如果 token 未被取消则调用该操作,另一种用于验证它是否被取消。这些测试看起来像:

[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 注入(inject) 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 编写其他测试,但它们似乎超出了当前问题的范围...

关于c# - 调用了单元测试 ThrowIfCancellationRequested(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21992428/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com