如何对异步调用的进度报告进行单元测试?
How to unit-test the progress report of an async call?
目标
我想测试一些接受 IProgress 的异步调用的进度报告。
我要验证:
- 如果这个报告的进度总是在[0.0; 1.0]
- 具有递增的值(上一个值 <= 下一个值)
- 在某些时候报告 1.0,例如最后一次通话
我想使用尽可能少的框架 -> 不使用另一个 3.party 库的解决方案会很好。
Problem/My 尝试
我试着写了一个简单的异步任务测试方法来实现这个。我调用方法来测试 await 例如。 await asyncCallToTest(new Progress((p) => { Assert.someting(p); }))
。也许在 lambda 中使用堆栈变量。完整示例稍后。
然而,这导致测试成功,即使它不应该是可能的,或者有时测试通过,然后在没有更改的情况下失败....
代码
我通过简单的独立测试重现了这些问题 class:
using System;
using System.Threading.Tasks;
using NUnit.Framework;
namespace FastProgressReporter
{
public class Tests
{
[Test]
public async Task SampleOkTest()
{
await TestReporter(new Progress<double>((p) => {
Assert.GreaterOrEqual(p, 0);
Assert.LessOrEqual(p, 1);
}));
//Sometimes fails, sometims passes. Why?
}
[Test]
public async Task SampleFailTest()
{
await TestReporter(new Progress<double>((p) => {
Assert.GreaterOrEqual(p, 2); // Has to Fail, however passes.
Assert.LessOrEqual(p, 3);
}));
}
[Test]
public async Task ReportsIncremental()
{
double latestProgress = 0;
await TestReporter(new Progress<double>((p) => {
p = 1 - p; //Force Decrement...
Assert.GreaterOrEqual(latestProgress, p); //Should fail on second call
latestProgress = p;
}));
}
[Test]
public async Task ReportsOneOneOrMoreTimes()
{
double maxProgress = 0;
await TestReporter(new Progress<double>((p) => {
maxProgress = p > maxProgress ? p : maxProgress;
}));
Assert.AreEqual(0, maxProgress); //Should always fail
//Sometimes fails, sometims passes. Why?
}
// Methode under test
public static async Task TestReporter(IProgress<double> progress)
{
progress.Report(0);
await Task.Run(() => { /*Do nothing, however await*/ });
progress.Report(0.5);
await Task.Run(() => { /*Do nothing, however await*/ });
progress.Report(1);
}
}
}
环境
VisualStudio 2019,.net-framework 4.7.2,NUnit3 测试项目
问题
我如何可靠、正确地实现我的目标?
相关问题
这使用了我试图避免的 3 方库,并且直接在测试方法内部调用 .Report(),而不是在测试调用的异步方法中调用
感谢您的帮助。
您可以将所有进度报告添加到列表中,然后对集合进行断言。项目的数量将等于进度报告的数量并且顺序相同。您可以检查与进度报告相关的任意数量的条件。
关于你的测试失败,这很奇怪,因为如果它们 运行 正确,那么每次你 运行 它们应该有相同的结果,因为不依赖于状态。唯一的依赖是测试运行器实际上 运行 异步测试。据我所知,NUnit 应该能够做到这一点,它支持异步测试。
Progress 实例捕获当前的 SynchronizationContext 并使用它运行回调。这对 UI 特别有用,因为进度回调将在 UI 线程上执行,因此您无需担心。 NUnit 很可能有自己的自定义 SynchronizationContext 用于执行异步测试,您的测试有时会失败,有时不会,这可能与相关测试实际完成后正在执行的进度报告有关。在这种情况下,您实际上可能会在一个不同的测试中得到一个测试的断言异常。
如果这是真的,那么您可能需要自己实现不使用 SynchronizationContext 的 IProgress 才能正确执行进度报告。基本上,您想要的是在调用报告方法时执行回调。您的实现可以做到这一点,无论何时调用进度方法,它都会将报告的值添加到列表中,该列表通过 属性 公开,然后使用 属性.[=10 执行断言=]
给你 st运行ge 结果的很可能是 Progress class 的使用,因为它在内部依赖于 SynchronizationContext 来执行回调。
解决方案
按照@Andrei15193 的建议,我尝试自己实现 IProgress 以摆脱 Progress class。
private class UnitTestProgress<T> : IProgress<T>
{
private Action<T> handler;
public UnitTestProgress(Action<T> handler)
{
this.hander = handler?? throw new ArgumentNullException(nameof(hander));
}
public void Report(T value)
{
handler(value);
}
}
我在这里选择了一个非常简单的class作为实现。
据我测试,如果 Progress 被我的新 class 替换,问题就解决了,单元测试是可靠的。
目标
我想测试一些接受 IProgress 的异步调用的进度报告。 我要验证:
- 如果这个报告的进度总是在[0.0; 1.0]
- 具有递增的值(上一个值 <= 下一个值)
- 在某些时候报告 1.0,例如最后一次通话
我想使用尽可能少的框架 -> 不使用另一个 3.party 库的解决方案会很好。
Problem/My 尝试
我试着写了一个简单的异步任务测试方法来实现这个。我调用方法来测试 await 例如。 await asyncCallToTest(new Progress((p) => { Assert.someting(p); }))
。也许在 lambda 中使用堆栈变量。完整示例稍后。
然而,这导致测试成功,即使它不应该是可能的,或者有时测试通过,然后在没有更改的情况下失败....
代码
我通过简单的独立测试重现了这些问题 class:
using System;
using System.Threading.Tasks;
using NUnit.Framework;
namespace FastProgressReporter
{
public class Tests
{
[Test]
public async Task SampleOkTest()
{
await TestReporter(new Progress<double>((p) => {
Assert.GreaterOrEqual(p, 0);
Assert.LessOrEqual(p, 1);
}));
//Sometimes fails, sometims passes. Why?
}
[Test]
public async Task SampleFailTest()
{
await TestReporter(new Progress<double>((p) => {
Assert.GreaterOrEqual(p, 2); // Has to Fail, however passes.
Assert.LessOrEqual(p, 3);
}));
}
[Test]
public async Task ReportsIncremental()
{
double latestProgress = 0;
await TestReporter(new Progress<double>((p) => {
p = 1 - p; //Force Decrement...
Assert.GreaterOrEqual(latestProgress, p); //Should fail on second call
latestProgress = p;
}));
}
[Test]
public async Task ReportsOneOneOrMoreTimes()
{
double maxProgress = 0;
await TestReporter(new Progress<double>((p) => {
maxProgress = p > maxProgress ? p : maxProgress;
}));
Assert.AreEqual(0, maxProgress); //Should always fail
//Sometimes fails, sometims passes. Why?
}
// Methode under test
public static async Task TestReporter(IProgress<double> progress)
{
progress.Report(0);
await Task.Run(() => { /*Do nothing, however await*/ });
progress.Report(0.5);
await Task.Run(() => { /*Do nothing, however await*/ });
progress.Report(1);
}
}
}
环境
VisualStudio 2019,.net-framework 4.7.2,NUnit3 测试项目
问题
我如何可靠、正确地实现我的目标?
相关问题
感谢您的帮助。
您可以将所有进度报告添加到列表中,然后对集合进行断言。项目的数量将等于进度报告的数量并且顺序相同。您可以检查与进度报告相关的任意数量的条件。
关于你的测试失败,这很奇怪,因为如果它们 运行 正确,那么每次你 运行 它们应该有相同的结果,因为不依赖于状态。唯一的依赖是测试运行器实际上 运行 异步测试。据我所知,NUnit 应该能够做到这一点,它支持异步测试。
Progress 实例捕获当前的 SynchronizationContext 并使用它运行回调。这对 UI 特别有用,因为进度回调将在 UI 线程上执行,因此您无需担心。 NUnit 很可能有自己的自定义 SynchronizationContext 用于执行异步测试,您的测试有时会失败,有时不会,这可能与相关测试实际完成后正在执行的进度报告有关。在这种情况下,您实际上可能会在一个不同的测试中得到一个测试的断言异常。
如果这是真的,那么您可能需要自己实现不使用 SynchronizationContext 的 IProgress 才能正确执行进度报告。基本上,您想要的是在调用报告方法时执行回调。您的实现可以做到这一点,无论何时调用进度方法,它都会将报告的值添加到列表中,该列表通过 属性 公开,然后使用 属性.[=10 执行断言=]
给你 st运行ge 结果的很可能是 Progress class 的使用,因为它在内部依赖于 SynchronizationContext 来执行回调。
解决方案
按照@Andrei15193 的建议,我尝试自己实现 IProgress 以摆脱 Progress class。
private class UnitTestProgress<T> : IProgress<T>
{
private Action<T> handler;
public UnitTestProgress(Action<T> handler)
{
this.hander = handler?? throw new ArgumentNullException(nameof(hander));
}
public void Report(T value)
{
handler(value);
}
}
我在这里选择了一个非常简单的class作为实现。
据我测试,如果 Progress 被我的新 class 替换,问题就解决了,单元测试是可靠的。