延迟后评估 NUnit 断言
Evaluate NUnit assertion after a delay
我正在尝试使用 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
约束仍然立即评估,而不是像 here.
所讨论的那样在 3 秒后评估
这个问题有更好的解决方案吗?
查理的回答解决了您的实际问题(他的回答总是如此)。但是,我强烈建议不要进行依赖于延迟的测试。如果您的测试失败,您如何知道问题是否是您等待的时间不够长。
此外,如果这是自动化测试套件的一部分,那么随着您添加更多这样的测试,执行所有测试所花费的时间将变得非常昂贵。
好的测试是确定性的。例如,请参阅 Martin Fowler 的 Eradicating Non-Determinism in Tests,其中包括短语:
Never use bare sleeps to wait for asynchonous responses: use a callback or polling.
我的建议是重构您的代码,以便将功能与线程分离,并测试功能,而不是线程部分。
一旦实现了这种分离,就可以为调用线程上的功能的代码提供一个 Mock,您可以简单地验证 Mock 是否被调用,因为您已经单独测试了它的行为是否正确,尽管仍然需要轮询...所以:
如果您封装启动单独线程的代码,那么在您的测试中,提供一个实际同步执行代码的模拟线程启动器。因此,一旦被测试的代码完成,您就可以进行断言,无需等待。
您对 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);
因为每次轮询时都会评估 属性。
我正在尝试使用 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
约束仍然立即评估,而不是像 here.
这个问题有更好的解决方案吗?
查理的回答解决了您的实际问题(他的回答总是如此)。但是,我强烈建议不要进行依赖于延迟的测试。如果您的测试失败,您如何知道问题是否是您等待的时间不够长。
此外,如果这是自动化测试套件的一部分,那么随着您添加更多这样的测试,执行所有测试所花费的时间将变得非常昂贵。
好的测试是确定性的。例如,请参阅 Martin Fowler 的 Eradicating Non-Determinism in Tests,其中包括短语:
Never use bare sleeps to wait for asynchonous responses: use a callback or polling.
我的建议是重构您的代码,以便将功能与线程分离,并测试功能,而不是线程部分。
一旦实现了这种分离,就可以为调用线程上的功能的代码提供一个 Mock,您可以简单地验证 Mock 是否被调用,因为您已经单独测试了它的行为是否正确,尽管仍然需要轮询...所以:
如果您封装启动单独线程的代码,那么在您的测试中,提供一个实际同步执行代码的模拟线程启动器。因此,一旦被测试的代码完成,您就可以进行断言,无需等待。
您对 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);
因为每次轮询时都会评估 属性。