我有一个方法可以简化为:
public static void InvokeBackground(Action action, CancellationToken cancellation)
{
_ = Task.Run(() => action, cancellation);
}
我正在尝试编写一个单元测试来验证
action
isn't 如果 cancellation
令牌已被取消,则被调用。
因此测试可能如下所示:
static CancellationToken CanceledToken { get; }
[Fact]
public async Task BackgroundInvoked_NotInvokedWhenCanceled()
{
// verify token is indeed canceled
Assert.True(CanceledToken.IsCancellationRequested);
// set flag if invoked
var invoked = false;
InvokeBackground(() => invoked = true, CanceledToken);
// delay to allow execution to proceed
await Task.Delay(10);
Assert.False(invoked);
}
但是有几个问题:
是否有更好的方法来测试这样的代码,或者更改代码以使其更具可测试性?或者这最终会导致我只需要决定“等待时间与测试准确性”曲线上我可以接受的位置吗?
只需决定“等待时间与测试准确性”曲线上我可以接受的位置吗?
我会说是的。
让我们取您在测试中等待的任何有限时间跨度值。因此,InvokeBackground 中可能存在错误,因此它比单元测试等待的时间更长(例如,您的时间跨度 + 1),并且只有在单元测试之后才运行该操作。换句话说,无论您对单元测试施加什么延迟,您测试的函数都可以运行更长时间并最终运行该操作。所以是的,一定存在一些限制。
对于这种特殊情况,我认为如果您定义 InvokeBackground 函数,如果令牌被取消,它会抛出 OperationCancelledException ,那就没问题了。
即
InvokeBackground(...)
{
token.ThrowIfCancellationRequested();
// ...
}
然后你就知道该函数已经退出了,你可以检查是否调用了action。
附注当然,可以编写 InvokeBackground 任务,以便此检查通过,即
InvokeBackground(Action action, CancellationToken token)
{
ThreadPool.QueueUserWorkItem(async _ =>
{
await Task.Delay(5000);
action();
});
token.ThrowIfCancellationReuested();
}
如果你检查是否创建了一个新线程,或者检查TaskScheduler,是否安排了一个新任务,我们可以重写这个函数,以便它启动外部进程,等等。
因此,应该在测试可以覆盖多少百分比的可能情况与其复杂性之间进行权衡。
我想说这样的事情都包含在代码审查中。
是否有更好的方法来测试这样的代码,或者更改代码以使其更具可测试性?
单元测试真正好的结果之一是它可以暴露薄弱的设计。
或者这最终会导致我只需要决定“等待时间与测试准确性”曲线上我可以接受的位置吗?
简短回答:是的。没有办法像这样“负面测试”即发即忘的代码。所以你必须选择一个合理的值。随着时间的推移,随着添加更多测试并且测试应用程序的线程池变得更加饱和,这变得不太合理。
长答案:您的单元测试为您提供有关您的应用程序的宝贵信息。
首先,考虑代码的语义以及为什么难以进行单元测试。
单元测试很难编写,因为它无法
await
在某个时刻完成工作(或者在本例中不完成)。单元测试不可能知道工作是否应该完成,因为实现是使用即发即弃(在本例中,实现只是“即发即弃”)。即发即忘的缺点之一是调用代码无法知道它何时完成。
接下来,考虑这对应用程序有何影响:应用程序无法知道其即发即弃代码何时完成,因此它无法知道何时适合关闭,因为可能存在一些即发即弃代码这还没有完成。因此,一般准则:即发即弃仅适用于可能无法实际运行的代码。 此类代码的用例非常小。我可以自信地说,我见过的所有“即发即弃”代码中至少有 98% 是完全错误的,因为开发人员
确实希望“即发即弃”代码运行,并且不运行该代码会导致语义错误。 单元测试非常适合深入了解代码,尤其是设计缺陷。请允许我建议您的单元测试
已经告诉您一些非常有用的事情:InvokeBackground
不应该存在。