如何将NUnit 3测试运行生成的XML结果反序列化为标准模型类?
NUnit 3自定义测试运行器XML结果反序列化方案
问题描述
我正在项目中集成自定义NUnit 3测试运行器,目前使用NUnit官方文档提供的代码:
var engine = TestEngineActivator.CreateInstance(); var package = new TestPackage("my.test.assembly.dll"); var runner = engine.GetRunner(package); XmlNode testResult = runner.Run(listener: null, TestFilter.Empty);
代码运行正常,能得到test-run格式的XmlNode测试结果(示例XML见下方)。我希望在NUnit相关程序集中找到反序列化该XML的辅助工具,但发现所有组件均仅以XML文档形式处理数据,手动读写单个属性或元素。请问是否存在官方方式(如NuGet包、框架类型等)将此XML反序列化为标准模型类?
测试结果示例XML
<test-run id="0" name="TestLibrary.Example.dll" fullname="...\tests\TestLibrary.Example.dll" runstate="Runnable" testcasecount="5" result="Passed" total="5" passed="5" failed="0" inconclusive="0" skipped="0" asserts="5" engine-version="3.11.1.0" clr-version="4.0.30319.42000" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.133984"> <command-line><![CDATA["myexe.exe"]]></command-line> <test-suite type="Assembly" id="0-1008" name="TestLibrary.Example.dll" fullname="TestLibrary.Example.dll" runstate="Runnable" testcasecount="5" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.053977" total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5"> <environment framework-version="3.11.0.0" clr-version="4.0.30319.42000" os-version="Microsoft Windows NT 10.0.19045.0" platform="Win32NT" cwd="..." machine-name="machine" user="user" user-domain="domain" culture="it-IT" uiculture="en-US" os-architecture="x64" /> <settings> <setting name="ImageRuntimeVersion" value="4.0.30319" /> <setting name="ImageTargetFrameworkName" value=".NETFramework,Version=v4.7.2" /> <setting name="ImageRequiresX86" value="False" /> <setting name="ImageRequiresDefaultAppDomainAssemblyResolver" value="False" /> <setting name="RuntimeFramework" value="net-4.0" /> <setting name="NumberOfTestWorkers" value="16" /> </settings> <properties> <property name="_PID" value="8204" /> <property name="_APPDOMAIN" value="domain-4461bdb5-TestLibrary.Example.dll" /> </properties> <test-suite type="TestSuite" id="0-1009" name="TestLibrary" fullname="TestLibrary" runstate="Runnable" testcasecount="5" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.038980" total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5"> <test-suite type="TestSuite" id="0-1010" name="Example" fullname="TestLibrary.Example" runstate="Runnable" testcasecount="5" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.038561" total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5"> <test-suite type="TestFixture" id="0-1000" name="ExampleTestFixture" fullname="TestLibrary.Example.ExampleTestFixture" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" testcasecount="5" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.035698" total="5" passed="5" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="5"> <test-suite type="ParameterizedMethod" id="0-1004" name="ParameterizedTest" fullname="TestLibrary.Example.ExampleTestFixture.ParameterizedTest" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" testcasecount="2" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.028787" total="2" passed="2" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="2"> <test-case id="0-1002" name="Example1" fullname="TestLibrary.Example.ExampleTestFixture.Example1" methodname="ParameterizedTest" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" seed="1295788249" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.022636" asserts="1"> <output><![CDATA[Running ExampleTestFixture.SetupMethod Running ExampleTestFixture.ParameterizedTest ]]></output> </test-case> <test-case id="0-1003" name="ParameterizedTest(2,3,5)" fullname="TestLibrary.Example.ExampleTestFixture.ParameterizedTest(2,3,5)" methodname="ParameterizedTest" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" seed="891989160" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.000241" asserts="1"> <output><![CDATA[Running ExampleTestFixture.SetupMethod Running ExampleTestFixture.ParameterizedTest ]]></output> </test-case> </test-suite> <test-case id="0-1001" name="SimpleTest" fullname="TestLibrary.Example.ExampleTestFixture.SimpleTest" methodname="SimpleTest" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" seed="202878501" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.001015" asserts="1"> <output><![CDATA[Running ExampleTestFixture.SetupMethod Running ExampleTestFixture.SimpleTest ]]></output> </test-case> <test-suite type="ParameterizedMethod" id="0-1007" name="TestWithSource" fullname="TestLibrary.Example.ExampleTestFixture.TestWithSource" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" testcasecount="2" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.000261" total="2" passed="2" failed="0" warnings="0" inconclusive="0" skipped="0" asserts="2"> <test-case id="0-1005" name="Example1" fullname="TestLibrary.Example.ExampleTestFixture.Example1" methodname="TestWithSource" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" seed="1768044693" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.000162" asserts="1"> <output><![CDATA[Running ExampleTestFixture.SetupMethod Running ExampleTestFixture.TestWithSource ]]></output> </test-case> <test-case id="0-1006" name="TestWithSource(2,3,5)" fullname="TestLibrary.Example.ExampleTestFixture.TestWithSource(2,3,5)" methodname="TestWithSource" classname="TestLibrary.Example.ExampleTestFixture" runstate="Runnable" seed="858491452" result="Passed" start-time="2023-01-11 13:53:13Z" end-time="2023-01-11 13:53:13Z" duration="0.000051" asserts="1"> <output><![CDATA[Running ExampleTestFixture.SetupMethod Running ExampleTestFixture.TestWithSource ]]></output> </test-case> </test-suite> </test-suite> </test-suite> </test-suite> </test-suite> </test-run>
解决方案
1. 官方内置类型反序列化
NUnit Engine本身提供了对应测试结果XML的强类型模型,直接引用NUnit.Engine NuGet包(版本与测试运行器保持一致,如示例中的3.11.1),即可使用以下核心类型:
TestRun:对应<test-run>根节点TestSuite:对应各级<test-suite>节点TestCase:对应<test-case>节点
通过XmlSerializer结合这些类型完成反序列化:
using System.Xml.Serialization; using NUnit.Engine; // 从XmlNode获取XML字符串 var xmlContent = testResult.OuterXml; var serializer = new XmlSerializer(typeof(TestRun)); using var reader = new StringReader(xmlContent); var testRunModel = (TestRun)serializer.Deserialize(reader);
2. 控制台运行器辅助类(可选)
如果需要更复杂的结果处理逻辑,可以引用NUnit.ConsoleRunner包,其中的ResultReporter等类可用于解析XML结果,但这类API偏向控制台工具内部实现,稳定性不如Engine核心类型。
3. 手动定义模型类(自定义场景)
若内置类型无法覆盖所有字段,可根据XML结构手动生成带序列化特性的模型类:
[XmlRoot("test-run")] public class CustomTestRun { [XmlAttribute("id")] public string Id { get; set; } [XmlAttribute("result")] public string Result { get; set; } [XmlElement("test-suite")] public CustomTestSuite RootSuite { get; set; } } public class CustomTestSuite { [XmlAttribute("type")] public string Type { get; set; } [XmlElement("test-suite")] public List<CustomTestSuite> ChildSuites { get; set; } [XmlElement("test-case")] public List<CustomTestCase> TestCases { get; set; } } public class CustomTestCase { [XmlAttribute("name")] public string Name { get; set; } [XmlAttribute("result")] public string Result { get; set; } [XmlElement("output")] public string Output { get; set; } }
之后用XmlSerializer反序列化即可。
内容的提问来源于stack exchange,提问作者Alberto Chiesa
相关产品推荐
相关产品推荐

