如何停止 NUnit ITestRunner?

How to stop NUnit ITestRunner?

使用 nunit.engine 3.10.0,我无法异步停止 运行 ITestRunnerTestPackage 设置为在本地执行,即 InProcess 和当前 AppDomain 中。第二次测试后没有像预期的那样开始更多测试,但是 while 循环永远不会结束。

public static void Main(string[] args)
{
    // 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
    string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
    string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
        new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));

    // Nunit 3.10.0
    var minVersion = new Version("3.4");
    ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);

    // configure a test package that executes
    // in the current process and in the current domain
    var testPackage = new TestPackage(testAssemblyFileNames);
    testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
    testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
    testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
    testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);

    ITestRunner testRunner = testEngine.GetRunner(testPackage);

    // prepare a listener that stops the test runner
    // when the second test has been started
    const bool StopAfterSecondTest = true;
    int testStartedCount = 0;
    var listener = new MyTestEventListener();
    listener.TestStarted += (sender, eventArgs) =>
    {
        testStartedCount++;
        if ( StopAfterSecondTest && testStartedCount == 2 )
        {
            testRunner.StopRun(force: true);
        }
    };

    var testFilterBuilder = new TestFilterBuilder();
    TestFilter testFilter = testFilterBuilder.GetFilter();

    ITestRun testRun = testRunner.RunAsync(listener, testFilter);

    bool keepRunning;
    int loopCount = 0;
    do
    {
        bool completed = testRun.Wait(500);
        bool running = testRunner.IsTestRunning;
        keepRunning = !completed && running;
        loopCount++;
    } while ( keepRunning );

    Console.WriteLine($"Loop count: {loopCount}");

    XmlNode resultNode = testRun.Result;
    Console.WriteLine(resultNode.InnerText);

    Console.ReadKey();
}

private class MyTestEventListener : ITestEventListener
{
    private const string TestCaseStartPrefix = "<start-test";
    private const string TestMethodTypeAttribute = " type=\"TestMethod\"";

    public event EventHandler<EventArgs> TestStarted;

    public void OnTestEvent(string report)
    {
        if ( report.StartsWith(TestCaseStartPrefix) &&
             report.Contains(TestMethodTypeAttribute) )
        {
            TestStarted?.Invoke(this, new EventArgs());
        }
    }
}

如果我跳过等待并尝试获取测试结果,我会得到 InvalidOperationException:'Cannot retrieve Result from an incomplete or cancelled TestRun.'

如何停止测试运​​行程序并获取停止前完成的测试结果?

您不能在测试中执行此操作。您的侦听器在测试本身的上下文中执行。出于这个原因,特别禁止听众试图改变测试的结果。此外,事件被缓冲,在这种情况下甚至可能直到测试 运行 完成后才被接收。

StopRun 旨在由主要 运行ner 本身调用,通常由某些用户输入触发。

您还应注意此问题:https://github.com/nunit/nunit/issues/3276 which prevents StopRun(true) from working under any circumstances. It was fixed in PR https://github.com/nunit/nunit/pull/3281 但尚未出现在框架的任何版本中。您将不得不使用框架的最新开发版本或切换到 StopRun(false).

基于@Charlie 的,这是修改代码以停止所有线程的方法:

public static void Main(string[] args)
{
    // 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
    // each test case includes a 200 ms delay
    string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
    string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
        new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));

    // Nunit 3.10.0
    var minVersion = new Version("3.4");
    ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);

    // configure a test package that executes
    // in the current process and in the current domain
    var testPackage = new TestPackage(testAssemblyFileNames);
    testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
    testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
    testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
    testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);

    ITestRunner testRunner = testEngine.GetRunner(testPackage);

    var listener = new TestStartListener();
    var testFilterBuilder = new TestFilterBuilder();
    TestFilter testFilter = testFilterBuilder.GetFilter();
    ITestRun testRun = testRunner.RunAsync(listener, testFilter);

    // wait until the first test case has been started
    while ( listener.Count < 1 )
    {
        Thread.Sleep(50);
    }

    bool keepRunning = true;
    while ( keepRunning )
    {
        int testStartedCount = listener.Count;
        testRunner.StopRun(force: false);
        Writer.WriteLine($"{GetTimeStamp()}, Stop requested after {testStartedCount} test cases.");

        // wait for less time than a single test needs to complete
        bool completed = testRun.Wait(100);
        bool running = testRunner.IsTestRunning;
        Writer.WriteLine($"{GetTimeStamp()} Completed: {completed}, running: {running}");
        keepRunning = !completed && running;
    }

    listener.WriteReportsTo(Writer);
    XmlNode resultNode = testRun.Result;
    Writer.WriteLine("Test result:");
    resultNode.WriteContentTo(ResultWriter);

    Console.ReadKey();
}

private class TestStartListener : List<string>, ITestEventListener
{
    private const string TestCaseStartPrefix = "<start-test";
    private const string TestMethodTypeAttribute = " type=\"TestMethod\"";

    public event EventHandler<EventArgs> TestStarted;

    public void OnTestEvent(string report)
    {
        if ( report.StartsWith(TestCaseStartPrefix) &&
             report.Contains(TestMethodTypeAttribute) )
        {
            Add($"{GetTimeStamp()}, {report}");
            TestStarted?.Invoke(this, new EventArgs());
        }
    }

    public void WriteReportsTo(TextWriter writer)
    {
        Writer.WriteLine($"Listener was called {Count} times.");
        foreach ( var report in this )
        {
            Writer.WriteLine(report);
        }
    }
}

两个测试程序集在运行程序的进程中执行,在单个域和两个线程上,每个测试程序集一个。总共执行并通过了两个测试方法;一个用于两个测试组件中的每一个。其他测试方法不会被执行也不会被报告。其他测试装置 (类) 不会被执行并被报告为 result="Failed" label="Cancelled".

注意testRunner.StopRun(force: false)被重复调用。如果只调用一次,其他线程将运行完成。