如何对异步调用的进度报告进行单元测试?

问题描述

目标

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

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

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

问题/我的尝试

我尝试编写一个简单的异步任务测试方法来实现这一点。我调用方法来测试等待,例如。 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(),而不是在测试调用异步方法

感谢您的帮助。

解决方法

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

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

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

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

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

,

解决方案

按照@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 被我的新类取代,问题就解决了,单元测试是可靠的。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...