【问题标题】:How to unit-test the progress report of an async call?如何对异步调用的进度报告进行单元测试?
【发布时间】:2021-05-27 16:12:30
【问题描述】:

目标

我想测试一些接受 IProgress 的异步调用的进度报告。 我要验证:

  • 如果此报告的进度始终在 [0.0; 1.0]
  • 具有递增的值(上一个值
  • 在某些时候报告 1.0,例如作为最后一次调用

我想使用尽可能少的框架 -> 不使用另一个 3. 方库的解决方案会很好。

问题/我的尝试

我尝试编写一个简单的异步任务测试方法来实现这一点。我调用方法来测试等待,例如。 await asyncCallToTest(new Progress((p) => { Assert.someting(p); }))。并且可能在 lambda 中使用堆栈变量。后面有完整的例子。

然而,这导致测试成功,即使它不应该是可能的,或者有时测试通过了,然后它没有改变就失败了......

代码

我用一个简单的独立测试类重新创建了问题:

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 测试项目

问题

我如何可靠且正确地实现我的目标?

相关问题

Verifying progress reported from an async call is properly report in unit tests 这使用了一个我试图避免的 3 方库,并且在测试方法中直接调用 .Report(),而不是在测试调用的异步方法中

感谢您的帮助。

【问题讨论】:

  • ty,固定@Fildor
  • 我会试试这个:让Progress 将所有值附加到列表中。然后在清单上做出你的断言。 (应该“排序”,不应该为空,应该有 X 项,第一项应该是 A,最后一项应该是 B ...)
  • 我会使用await Task.Delay(someMillis); 而不是await Task.Run( () =&gt; { /* Nothing really */ });
  • 使用 Task.Delay 或 Task.Yield 而不是 Task.Run。变化不大,但这些方法更适合您希望通过 Task.Run 实现的目标。

标签: c# unit-testing asynchronous .net-4.0 nunit-3.0


【解决方案1】:

解决方案

根据@Andrei15193 的建议,我尝试自己实现 IProgress 以摆脱 Progress 类。

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);
    }
}

我在这里选择了一个非常简单的类作为实现。

据我测试,如果 Progress 被我的新类替换,问题就解决了,并且单元测试是可靠的。

【讨论】:

    【解决方案2】:

    您可以将所有进度报告添加到列表中,然后对集合执行断言。项目的数量将等于进度报告的数量,并且顺序相同。您可以检查与进度报告相关的任意数量的条件。

    关于您的测试失败,这很奇怪,因为如果它们运行正确,那么每次运行它们时它们应该具有相同的结果,因为不依赖于状态。唯一的依赖是实际运行异步测试的测试运行器。 NUnit 应该能够做到这一点,据我所知它支持异步测试。

    Progress 实例捕获当前的 SynchronizationContext 并使用它运行回调。这对 UI 特别有用,因为进度回调将在 UI 线程上执行,因此您无需担心。 NUnit 很可能有自己的自定义 SynchronizationContext 用于执行异步测试,您的测试有时会失败,有时可能与相关测试实际完成后正在执行的进度报告无关。在这种情况下,您实际上可能会在不同的测试中获得一个测试的断言异常。

    如果这是真的,那么您可能需要拥有自己的不使用 SynchronizationContext 的 IProgress 实现,以便正确执行进度报告。基本上,您想要的是在调用报告方法时执行回调。您的实现完全可以做到这一点,每当您调用 progress 方法时,它会将报告的值添加到通过属性公开的列表中,然后使用该属性执行断言。

    给你带来奇怪结果的很可能是 Progress 类的使用,因为它在内部依赖于 SynchronizationContext 来执行回调。

    【讨论】:

    • 好的,我通过将报告的进度添加到列表来测试它,然后断言 -> 仍然是随机且不正确的结果 ``` [Test] public async Task SampleFailTest() { ListreportData = 新列表(); await TestReporter(new Progress((p) => {reportData.Add(p); })); foreach(reportData 中的变量 e){ Assert.GreaterOrEqual(2, e); // 必须失败,但仍有可能通过。断言.LessOrEqual(-1, e); } } ``` 是的可能是 Progress 类本身
    • @ProfBits 它很可能是 Progress 实例。尝试使用自定义 IProgress 实现,这只是一种方法。
    猜你喜欢
    • 2015-09-28
    • 2016-03-19
    • 2020-12-22
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 2013-03-21
    • 2013-03-15
    相关资源
    最近更新 更多