You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过NUnit.Engine以独立进程编程运行NUnit测试

问题:如何通过NUnit Engine以独立进程运行单元测试?

我基于.NET 7.0开发了一款C#应用,通过NUnit 3.13.3和NUnit.Engine 3.16.3以编程方式加载并执行单元测试,测试同样基于.NET 7.0开发。

当前测试与应用在同一进程运行,引发以下问题:

  • 若应用与测试存在共同依赖,测试会使用应用的依赖版本而非自身的,版本存在差异时会引发问题;
  • 当应用尝试重载测试时,测试DLL可被重载,但它的依赖无法重载(无论是否与应用存在共同依赖)。

我了解nunit-console会在独立进程中运行测试,这能解决我的问题,但不知如何通过编程实现这一功能。目前通过ITestRunner runner = TestEngineActivator.CreateInstance().GetRunner(_testPackage);获取的Runner始终在应用进程内运行,无论TestPackage配置何种设置。

我的问题是:如何获取可在独立进程运行测试的Runner?TestPackage需要配置哪些设置?是否需要额外安装NuGet包?


相关代码实现

获取ITestEngine

public TestModel() {
    _engine = TestEngineActivator.CreateInstance();
    _settingsService = new SettingsService(true);
    _settingsService.LoadSettings();
    _engine.InternalTraceLevel = InternalTraceLevel.Off;
}

加载测试

public async Task<XmlNode?> LoadTestsAsync(string dllTestFile) {
    LoadedTestFile = dllTestFile;
    _testPackage = new TestPackage(LoadedTestFile);
    _runner = _engine.GetRunner(_testPackage);
    var nodes = await Task.Run(() => _runner.Explore(TestFilter.Empty));

    XmlWriter writer = XmlWriter.Create("tests.xml", new XmlWriterSettings() { Indent = true});
    nodes.WriteContentTo(writer);
    writer.Flush();
    writer.Dispose();

    return nodes;
}

运行测试

public void Run(TestNode testNode) {
    var filterService = _engine?.Services.GetService<ITestFilterService>();
    var builder = filterService?.GetTestFilterBuilder();
    CheckVirtualAndAddChildren(builder, testNode);
    _runner?.RunAsync(_events, builder?.GetFilter());
}

重载测试

public async Task<XmlNode?> ReloadAsync() {        
    try {
        _runner?.Unload();
    } catch (NUnitEngineUnloadException) {}
    _runner?.Dispose();
    _runner = _engine.GetRunner(_testPackage);
    
    var nodes = await Task.Run(() => _runner?.Explore(TestFilter.Empty));

    XmlWriter writer = XmlWriter.Create("tests.xml", new XmlWriterSettings() { Indent = true});
    nodes?.WriteContentTo(writer);
    writer.Flush();
    writer.Dispose();

    return nodes;
}

ITestRunner.Explore返回的XML

<?xml version="1.0" encoding="utf-8"?>
<test-suite type="Assembly" id="0-1007" name="NUnitTests.dll" fullname="H:/MyApplication/sample/NUnitDemo/NUnitTests/bin/Debug/net7.0/NUnitTests.dll" runstate="Runnable" testcasecount="4">
  <environment framework-version="3.13.3.0" clr-version="7.0.5" os-version="Microsoft Windows 10.0.19045" platform="Win32NT" cwd="H:\MyApplication\MyApplication\bin\Debug\net7.0" machine-name="XXXX" user="myself" user-domain="XXXX" culture="de-DE" uiculture="de-DE" os-architecture="x64" />
  <settings>
    <setting name="NumberOfTestWorkers" value="16" />
  </settings>
  <properties>
    <property name="_PID" value="29016" />
    <property name="_APPDOMAIN" value="MyApplication" />
  </properties>
  <test-suite type="TestSuite" id="0-1008" name="NUnitTests" fullname="NUnitTests" runstate="Runnable" testcasecount="4">
    <test-suite type="TestFixture" id="0-1000" name="DemoTests" fullname="NUnitTests.DemoTests" classname="NUnitTests.DemoTests" runstate="Runnable" testcasecount="4">
      <test-suite type="ParameterizedMethod" id="0-1006" name="BaseDependency" fullname="NUnitTests.DemoTests.BaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" testcasecount="2">
        <test-case id="0-1004" name="BaseDependency(Cycle #1)" fullname="NUnitTests.DemoTests.BaseDependency(Cycle #1)" methodname="BaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" seed="1298419094" />
        <test-case id="0-1005" name="BaseDependency(Cycle #2)" fullname="NUnitTests.DemoTests.BaseDependency(Cycle #2)" methodname="BaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" seed="2074295330" />
      </test-suite>
      <test-suite type="ParameterizedMethod" id="0-1003" name="NoBaseDependency" fullname="NUnitTests.DemoTests.NoBaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" testcasecount="2">
        <test-case id="0-1001" name="NoBaseDependency(Cycle #1)" fullname="NUnitTests.DemoTests.NoBaseDependency(Cycle #1)" methodname="NoBaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" seed="1168845109" />
        <test-case id="0-1002" name="NoBaseDependency(Cycle #2)" fullname="NUnitTests.DemoTests.NoBaseDependency(Cycle #2)" methodname="NoBaseDependency" classname="NUnitTests.DemoTests" runstate="Runnable" seed="64791598" />
      </test-suite>
    </test-suite>
  </test-suite>
</test-suite>

解决方案

要让NUnit Engine在独立进程中运行测试,只需配置TestPackage启用进程隔离,引擎会自动返回进程外Runner,具体步骤如下:

1. 配置TestPackage的进程隔离参数

在创建TestPackage后添加以下设置:

_testPackage = new TestPackage(LoadedTestFile);
// 启用独立进程模式:Separate为每个测试包开一个进程,Multiple为每个程序集开一个进程
_testPackage.AddSetting("ProcessModel", "Separate");
// 明确指定测试运行的.NET版本,避免版本兼容问题
_testPackage.AddSetting("RuntimeFramework", "net7.0");

2. 无需额外修改Runner获取逻辑

当配置了ProcessModel=Separate后,_engine.GetRunner(_testPackage)会自动返回进程外Runner(ProcessRunner),测试将在独立进程中执行,自动解决依赖版本冲突和DLL重载问题。

3. NuGet包检查

你当前使用的NUnit.Engine 3.16.3已包含进程隔离功能,无需额外安装其他包。只需确保测试项目的NUnit版本(3.13.3)与引擎版本兼容即可,当前版本组合是兼容的。

修改后的加载测试代码

public async Task<XmlNode?> LoadTestsAsync(string dllTestFile) {
    LoadedTestFile = dllTestFile;
    _testPackage = new TestPackage(LoadedTestFile);
    // 添加进程隔离配置
    _testPackage.AddSetting("ProcessModel", "Separate");
    _testPackage.AddSetting("RuntimeFramework", "net7.0");
    
    _runner = _engine.GetRunner(_testPackage);
    var nodes = await Task.Run(() => _runner.Explore(TestFilter.Empty));

    XmlWriter writer = XmlWriter.Create("tests.xml", new XmlWriterSettings() { Indent = true});
    nodes.WriteContentTo(writer);
    writer.Flush();
    writer.Dispose();

    return nodes;
}

验证进程隔离是否生效

运行测试后,查看ITestRunner.Explore返回的XML中的_PID属性,若每次运行测试的PID不同,说明测试在独立进程中执行;同时_APPDOMAIN会显示测试进程的AppDomain名称,而非主应用的。

重载测试的优化

使用进程隔离后,重载测试时只需正常调用_runner.Unload()和重新创建Runner即可,测试进程会被销毁并重新启动,所有依赖DLL都会被重新加载,彻底解决之前的重载问题。


内容的提问来源于stack exchange,提问作者user23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:05:56