我正在尝试使用 NUnit 为一种可能需要 1 到 3 秒才能完成的方法编写单元测试。为了使测试有效,我需要做的就是检查List<string> entries
是否在 1 到 3 秒的跨度内递增。
我目前的解决方案是使用Thread.Sleep()
:
1. int currentEntries = entries.count;
2. Call methodA()
3. Thread.Sleep(3000);
4. int updatedEntries = entries.count;
5. Assert.That(updatedEntries, Is.EqualTo(currentEntries+1));
此解决方案始终至少需要 3 秒,即使 methodA(( 完成得更快也是如此。
我尝试使用 NUnits 延迟约束:
Assert.That(entries.Count, Is.EqualTo(currentEntries+1).After(3).Seconds.PollEvery(250).MilliSeconds);
这本来是理想的,因为它支持轮询。但是,After
约束仍会立即评估,而不是在 3 秒后进行评估,如此处所述。
有没有更好的解决方案来解决这个问题?
对Assert.That
的调用有一个实际参数,该参数在调用方法之前立即计算。取entries.Count
的值,并将生成的整数值复制为方法参数。在方法中,我们正在处理一个常量。
当约束每 250 毫秒重新计算一次时,每次都会针对相同的复制常量完成,当然永远不会更改。
当您使用延迟约束(带或不带轮询(时,实际参数必须采用委托、lambda 或对字段的引用的形式。以下简单的修改应该使其工作。
Assert.That (() => entries.Count, Is.EqualTo(currentEntries+1).After(3).Seconds.PollEvery(250).MilliSeconds);
或者,这也应该有效
Assert.That (entries, Has.Count.EqualTo(currentEntries+1).After(3).Seconds.PollEvery(250).Milliseconds);
因为每次轮询时都会评估该属性。
查理的回答抓住了你的实际问题(就像他的回答总是一样(。但是,我强烈建议不要进行依赖于延迟的测试。如果你的测试失败了,你怎么知道问题是否在于你没有等待足够长的时间。
此外,如果这是自动测试套件的一部分,则随着您添加更多此类测试,执行所有测试所花费的时间将变得非常昂贵。
好的测试是确定性的。例如,参见Martin Fowler的T根除测试中的非确定性,其中包括以下短语:
切勿使用裸睡眠来等待异步响应:使用回调或轮询。
我的建议是重构您的代码,以便将功能与线程分开,并测试功能,而不是线程部分。
一旦实现了这种分离,就可以为在线程上调用功能的代码提供一个 Mock,您可以简单地验证是否调用了 Mock,因为您已经单独测试了它的行为是否正确,尽管这仍然需要轮询......所以:
如果您封装了启动单独线程的代码,那么在您的测试中,提供一个模拟线程启动器,它实际上同步执行代码。因此,一旦要测试的代码完成,您就可以进行断言,而无需等待。