为什么 NUnit 在线程挂起时不开始执行下一个测试?

Why does NUnit not start executing the next test when a thread is suspended?

我想知道为什么 NUnit 在调用 await Task.Delay 时不启动下一个异步测试。

我有一个测试项目,其中每个测试都向云中的服务提交作业,轮询结果,然后断言返回的结果。云服务可以 运行 并行处理数千个作业。在生产中,一项工作最多可能需要几个小时才能完成 运行,但我提交的测试工作每个 运行 不到一分钟。

目前并行最多 10 个测试 运行,但每个测试同步等待结果返回,然后才能开始下一个测试。

我正在想办法让所有测试更快地完成(我了解单元测试。我们也有,但这些测试有不同的目的)。一个想法是使用 .Net 中内置的 async/await 功能来暂停第一个测试正在 运行 开启的线程,并在第一个测试等待来自云服务的结果时开始下一个测试。我构建了一个小测试项目,看看这个想法是否可行。

这些测试是在 .Net Framework v4.7.2 上编写的。使用 Nunit 3.13.2.

using NUnit.Framework;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;

namespace TestNUnit
{
  [TestFixture]
  public class Test
  {
    private static ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();

    [Test]
    public async Task Test1()
    {
      _queue.Enqueue($"Starting test 1 {DateTime.Now}");
      await Task.Delay(12000);
      Assert.IsTrue(true);
      _queue.Enqueue($"  Ending test 1 {DateTime.Now}");
    }

    [Test]
    public async Task Test2()
    {
      _queue.Enqueue($"Starting test 2 {DateTime.Now}");
      await Task.Delay(10000);
      Assert.IsTrue(true);
      _queue.Enqueue($"  Ending test 2 {DateTime.Now}");
    }


    [Test]
    public async Task Test3()
    {
      _queue.Enqueue($"Starting test 3 {DateTime.Now}");
      await Task.Delay(14000);
      Assert.IsTrue(true);
      _queue.Enqueue($"  Ending test 3 {DateTime.Now}");
    }

    [OneTimeTearDown]
    public void Cleanup()
    {
      File.AppendAllLines("C:\temp\nunit.txt", _queue.ToArray() );
    }
  }
}

在我的 AssemblyInfo 文件中,我将以下行添加到 运行 2 个并行测试中。

[assembly: Parallelizable(ParallelScope.All)]
[assembly: LevelOfParallelism(2)]

我希望看到所有 3 个测试都在任何测试完成之前开始。相反,我看到的是 2 个测试开始,第三个测试在其他两个测试中的一个完成后开始。我使用 NUnitLite nuget 包在 Visual Studio 和命令行中尝试了 运行ning。

我在 XUnit 和 MsTest v2 上尝试了相同的测试。 运行使用 XUnit 的命令行版本时,我看到了我想要看到的行为。在其他任何地方,尽管我看到 1 个测试必须在第三个测试开始之前完成。

有谁知道为什么这只在 运行 命令行版本的 XUnit 时有效?谢谢!

这是测试框架在开始第三个测试之前等待 1 个测试完成时的输出:

Starting test 2 3/4/2022 7:40:18 AM
Starting test 1 3/4/2022 7:40:18 AM
  Ending test 2 3/4/2022 7:40:28 AM
Starting test 3 3/4/2022 7:40:28 AM
  Ending test 1 3/4/2022 7:40:30 AM
  Ending test 3 3/4/2022 7:40:42 AM

这是所有三个测试在任何完成之前开始时的输出:

Starting test 1 3/3/2022 4:19:09 PM
Starting test 3 3/3/2022 4:19:09 PM
Starting test 2 3/3/2022 4:19:09 PM
  Ending test 2 3/3/2022 4:19:19 PM
  Ending test 1 3/3/2022 4:19:21 PM
  Ending test 3 3/3/2022 4:19:23 PM

NUnit(很可能是其他框架 运行 对您不起作用的框架)设置您为 运行 测试指定的线程数。因此,当您到达所有这些测试都进入等待状态的点时,无法进行进一步的测试 运行.

此设计中的假设是等待状态很少且很短,这对于单元测试几乎总是正确的,这是大多数 运行ners 的原始预期应用程序 - 最肯定是 nunilitlite。

要让 运行ner 在所有最初分配的线程都在等待之后继续,必须 re-designed 动态创建新线程。

更新:

Responding to the comment about the ThreadPool... The NUnit parallel dispatcher doesn't use the ThreadPool. At the time I wrote it, the ThreadPool wasn't available on all platforms we supported, so each TestWorker creates its own thread. If it were written today, it might well use the ThreadPool as you imagined.

恐怕这可能会回答您的标题问题,但实际上对您没有帮助。但它确实提出了一种解决方法...

由于您将获得的唯一线程是最初分配的线程,因此您可以简单地增加线程数。例如...如果您估计您的特定测试集将有 50% 的时间处于等待状态,请使用 20 个线程而不是 10 个。试验该数字,直到您达到工作负载的最佳水平。如果理想数字在您的桌面上与您的 CI/CD 环境中不同(很可能),则将其作为选项提供给命令,而不是使用属性。