From 04e24ca15acb9e0d84641640cd553cc908e1103e Mon Sep 17 00:00:00 2001 From: Koji Hasegawa <hasegawa@hubsys.co.jp> Date: Mon, 6 May 2024 20:11:09 +0900 Subject: [PATCH 1/5] Add JUnitXmlWriter --- Editor/JUnitXmlWriter.cs | 41 +++++++++++++ Editor/JUnitXmlWriter.cs.meta | 2 + Editor/TestRunnerCallbacksImpl.cs | 41 +++++++++++++ Editor/TestRunnerCallbacksImpl.cs.meta | 3 + RuntimeInternals/CommandLineArgs.cs | 19 ++++++ Tests/Editor/JUnitXmlWriterTest.cs | 46 ++++++++++++++ Tests/Editor/JUnitXmlWriterTest.cs.meta | 2 + Tests/Editor/TestDoubles.meta | 3 + .../TestDoubles/FakeTestResultAdaptor.cs | 54 ++++++++++++++++ .../TestDoubles/FakeTestResultAdaptor.cs.meta | 3 + Tests/Editor/TestResources.meta | 3 + Tests/Editor/TestResources/junit.xml | 10 +++ Tests/Editor/TestResources/junit.xml.meta | 7 +++ Tests/Editor/TestResources/nunit3.xml | 61 +++++++++++++++++++ Tests/Editor/TestResources/nunit3.xml.meta | 7 +++ Tests/RuntimeInternals/CommandLineArgsTest.cs | 14 +++++ 16 files changed, 316 insertions(+) create mode 100644 Editor/JUnitXmlWriter.cs create mode 100644 Editor/JUnitXmlWriter.cs.meta create mode 100644 Editor/TestRunnerCallbacksImpl.cs create mode 100644 Editor/TestRunnerCallbacksImpl.cs.meta create mode 100644 Tests/Editor/JUnitXmlWriterTest.cs create mode 100644 Tests/Editor/JUnitXmlWriterTest.cs.meta create mode 100644 Tests/Editor/TestDoubles.meta create mode 100644 Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs create mode 100644 Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs.meta create mode 100644 Tests/Editor/TestResources.meta create mode 100644 Tests/Editor/TestResources/junit.xml create mode 100644 Tests/Editor/TestResources/junit.xml.meta create mode 100644 Tests/Editor/TestResources/nunit3.xml create mode 100644 Tests/Editor/TestResources/nunit3.xml.meta diff --git a/Editor/JUnitXmlWriter.cs b/Editor/JUnitXmlWriter.cs new file mode 100644 index 0000000..605fa18 --- /dev/null +++ b/Editor/JUnitXmlWriter.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.IO; +using System.Xml; +using System.Xml.XPath; +using System.Xml.Xsl; +using UnityEditor.TestTools.TestRunner.Api; + +namespace TestHelper.Editor +{ + public static class JUnitXmlWriter + { + private const string XsltPath = "Packages/com.nowsprinting.test-helper/Editor/nunit3-junit/nunit3-junit.xslt"; + // Note: This XSLT file is copied from https://github.com/nunit/nunit-transforms/tree/master/nunit3-junit + + public static void WriteTo(ITestResultAdaptor result, string path) + { + // Input + var nunit3XmlStream = new MemoryStream(); + var nunit3Writer = XmlWriter.Create(nunit3XmlStream); + result.ToXml().WriteTo(nunit3Writer); + var nunit3Xml = new XPathDocument(nunit3XmlStream); + + // Create output directory if it does not exist. + var directory = Path.GetDirectoryName(path); + if (directory != null && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // Output (JUnit XML) + var writer = XmlWriter.Create(path); + + // Execute the transformation. + var transformer = new XslCompiledTransform(); + transformer.Load(XsltPath); + transformer.Transform(nunit3Xml, writer); + } + } +} diff --git a/Editor/JUnitXmlWriter.cs.meta b/Editor/JUnitXmlWriter.cs.meta new file mode 100644 index 0000000..f6aa9e3 --- /dev/null +++ b/Editor/JUnitXmlWriter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4dc20ae42ef14abd914e0200e5b9e36b \ No newline at end of file diff --git a/Editor/TestRunnerCallbacksImpl.cs b/Editor/TestRunnerCallbacksImpl.cs new file mode 100644 index 0000000..a258882 --- /dev/null +++ b/Editor/TestRunnerCallbacksImpl.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using TestHelper.RuntimeInternals; +using UnityEditor; +using UnityEditor.TestTools.TestRunner.Api; +using UnityEngine; + +namespace TestHelper.Editor +{ + public class TestRunnerCallbacksImpl : ICallbacks + { + [InitializeOnLoadMethod] + private static void SetupCallbacks() + { + var api = ScriptableObject.CreateInstance<TestRunnerApi>(); + api.RegisterCallbacks(new TestRunnerCallbacksImpl()); + } + + public void RunStarted(ITestAdaptor testsToRun) + { + } + + public void RunFinished(ITestResultAdaptor result) + { + var path = CommandLineArgs.GetJUnitResultsPath(); + if (path != null) + { + JUnitXmlWriter.WriteTo(result, path); + } + } + + public void TestStarted(ITestAdaptor test) + { + } + + public void TestFinished(ITestResultAdaptor result) + { + } + } +} diff --git a/Editor/TestRunnerCallbacksImpl.cs.meta b/Editor/TestRunnerCallbacksImpl.cs.meta new file mode 100644 index 0000000..ca6e86e --- /dev/null +++ b/Editor/TestRunnerCallbacksImpl.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f474f20a34e94893ae5d168787df181b +timeCreated: 1714972666 \ No newline at end of file diff --git a/RuntimeInternals/CommandLineArgs.cs b/RuntimeInternals/CommandLineArgs.cs index 5714e33..a4a225d 100644 --- a/RuntimeInternals/CommandLineArgs.cs +++ b/RuntimeInternals/CommandLineArgs.cs @@ -54,5 +54,24 @@ public static string GetScreenshotDirectory(string[] args = null) return Path.Combine(Application.persistentDataPath, "TestHelper", "Screenshots"); } } + + /// <summary> + /// JUnit XML report save path. + /// </summary> + /// <returns></returns> + public static string GetJUnitResultsPath(string[] args = null) + { + const string JUnitResultsKey = "-testHelperJUnitResults"; + + try + { + args = args ?? Environment.GetCommandLineArgs(); + return DictionaryFromCommandLineArgs(args)[JUnitResultsKey]; + } + catch (KeyNotFoundException) + { + return null; + } + } } } diff --git a/Tests/Editor/JUnitXmlWriterTest.cs b/Tests/Editor/JUnitXmlWriterTest.cs new file mode 100644 index 0000000..b66b957 --- /dev/null +++ b/Tests/Editor/JUnitXmlWriterTest.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.IO; +using NUnit.Framework; +using TestHelper.Editor.TestDoubles; + +namespace TestHelper.Editor +{ + [TestFixture] + public class JUnitXmlWriterTest + { + private const string TestResourcesPath = "Packages/com.nowsprinting.test-helper/Tests/Editor/TestResources"; + private const string TestOutputDirectoryPath = "Logs/JUnitXmlWriterTest"; + // Note: relative path from the project root directory. + + [Test, Order(0)] + public void WriteTo_DirectoryDoesNotExist_CreateDirectoryAndWriteToFile() + { + if (Directory.Exists(TestOutputDirectoryPath)) + { + Directory.Delete(TestOutputDirectoryPath, true); + } + + var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); + var result = new FakeTestResultAdaptor(nunitXmlPath); + var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); + JUnitXmlWriter.WriteTo(result, path); + + Assert.That(path, Does.Exist); + } + + [Test] + public void WriteTo_WriteToFile() + { + var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); + var result = new FakeTestResultAdaptor(nunitXmlPath); + var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); + JUnitXmlWriter.WriteTo(result, path); + + var actual = File.ReadAllText(path); + var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); + Assert.That(actual, Is.EqualTo(expected)); + } + } +} diff --git a/Tests/Editor/JUnitXmlWriterTest.cs.meta b/Tests/Editor/JUnitXmlWriterTest.cs.meta new file mode 100644 index 0000000..95c1c25 --- /dev/null +++ b/Tests/Editor/JUnitXmlWriterTest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: abb979e3bb6b4ddd8e55f15691105f22 \ No newline at end of file diff --git a/Tests/Editor/TestDoubles.meta b/Tests/Editor/TestDoubles.meta new file mode 100644 index 0000000..8fa1fc2 --- /dev/null +++ b/Tests/Editor/TestDoubles.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1b70d94c46ae4c5ead3ad474b63d0c94 +timeCreated: 1714994775 \ No newline at end of file diff --git a/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs new file mode 100644 index 0000000..2f59f78 --- /dev/null +++ b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System; +using System.Collections.Generic; +using System.IO; +using NUnit.Framework.Interfaces; +using UnityEditor.TestTools.TestRunner.Api; +using TestStatus = UnityEditor.TestTools.TestRunner.Api.TestStatus; + +namespace TestHelper.Editor.TestDoubles +{ + /// <summary> + /// Implemented only <c>ToXml</c> method. + /// </summary> + public class FakeTestResultAdaptor : ITestResultAdaptor + { + private readonly string _path; + + /// <summary> + /// Constructor. + /// </summary> + /// <param name="path">NUnit3 XML file path used in <c>ToXml</c> method.</param> + public FakeTestResultAdaptor(string path) + { + _path = path; + } + + public TNode ToXml() + { + var xmlText = File.ReadAllText(_path); + return TNode.FromXml(xmlText); + } + + public ITestAdaptor Test { get { throw new NotImplementedException(); } } + public string Name { get { throw new NotImplementedException(); } } + public string FullName { get { throw new NotImplementedException(); } } + public string ResultState { get { throw new NotImplementedException(); } } + public TestStatus TestStatus { get { throw new NotImplementedException(); } } + public double Duration { get { throw new NotImplementedException(); } } + public DateTime StartTime { get { throw new NotImplementedException(); } } + public DateTime EndTime { get { throw new NotImplementedException(); } } + public string Message { get { throw new NotImplementedException(); } } + public string StackTrace { get { throw new NotImplementedException(); } } + public int AssertCount { get { throw new NotImplementedException(); } } + public int FailCount { get { throw new NotImplementedException(); } } + public int PassCount { get { throw new NotImplementedException(); } } + public int SkipCount { get { throw new NotImplementedException(); } } + public int InconclusiveCount { get { throw new NotImplementedException(); } } + public bool HasChildren { get { throw new NotImplementedException(); } } + public IEnumerable<ITestResultAdaptor> Children { get { throw new NotImplementedException(); } } + public string Output { get { throw new NotImplementedException(); } } + } +} diff --git a/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs.meta b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs.meta new file mode 100644 index 0000000..f5c4ee1 --- /dev/null +++ b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 91128e261ace43cfb04e5ea70971677a +timeCreated: 1714994808 \ No newline at end of file diff --git a/Tests/Editor/TestResources.meta b/Tests/Editor/TestResources.meta new file mode 100644 index 0000000..5f3d813 --- /dev/null +++ b/Tests/Editor/TestResources.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d63ee51a7f8948909631726b0a5a269d +timeCreated: 1714990355 \ No newline at end of file diff --git a/Tests/Editor/TestResources/junit.xml b/Tests/Editor/TestResources/junit.xml new file mode 100644 index 0000000..5749df1 --- /dev/null +++ b/Tests/Editor/TestResources/junit.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?><testsuites tests="3" failures="0" disabled="0" time="0.2538825"><testsuite tests="1" time="0.092622" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_CreateNewSceneWithCameraAndLight" assertions="0" time="0.036802" status="Passed" classname="TestHelper.Editor.CreateSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml +</system-out></testcase></testsuite><testsuite tests="1" time="0.089184" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_LoadedSceneNotInBuild" assertions="0" time="0.053176" status="Passed" classname="TestHelper.Editor.LoadSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml +</system-out></testcase></testsuite><testsuite tests="1" time="0.055223" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" assertions="0" time="0.014506" status="Passed" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml +[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report +Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor, +Excluded Assemblies: *.Tests +Included Paths: <Not specified> +Excluded Paths: <Not specified> +Saving results to: /github/workspace/artifacts/editmode-results.xml +</system-out></testcase></testsuite></testsuites> \ No newline at end of file diff --git a/Tests/Editor/TestResources/junit.xml.meta b/Tests/Editor/TestResources/junit.xml.meta new file mode 100644 index 0000000..b047d49 --- /dev/null +++ b/Tests/Editor/TestResources/junit.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 85243ab70dec645acb9374c9fbf196b1 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/TestResources/nunit3.xml b/Tests/Editor/TestResources/nunit3.xml new file mode 100644 index 0000000..fd467eb --- /dev/null +++ b/Tests/Editor/TestResources/nunit3.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<test-run id="2" testcasecount="3" result="Passed" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.2538825"> + <test-suite type="TestSuite" id="1000" name="UnityProject~" fullname="UnityProject~" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.253883" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties> + <property name="platform" value="EditMode" /> + </properties> + <test-suite type="Assembly" id="1009" name="TestHelper.Editor.Tests.dll" fullname="/github/workspace/UnityProject~/Library/ScriptAssemblies/TestHelper.Editor.Tests.dll" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.246003" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties> + <property name="_PID" value="3301" /> + <property name="_APPDOMAIN" value="Unity Child Domain" /> + <property name="platform" value="EditMode" /> + <property name="EditorOnly" value="True" /> + </properties> + <test-suite type="TestSuite" id="1010" name="TestHelper" fullname="TestHelper" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.245391" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties /> + <test-suite type="TestSuite" id="1011" name="Editor" fullname="TestHelper.Editor" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.241916" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties /> + <test-suite type="TestFixture" id="1003" name="CreateSceneAttributeTest" fullname="TestHelper.Editor.CreateSceneAttributeTest" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.092622" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties /> + <test-case id="1004" name="Attach_CreateNewSceneWithCameraAndLight" fullname="TestHelper.Editor.CreateSceneAttributeTest.Attach_CreateNewSceneWithCameraAndLight" methodname="Attach_CreateNewSceneWithCameraAndLight" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" seed="1087926775" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.036802" asserts="0"> + <properties> + <property name="retryIteration" value="0" /> + <property name="repeatIteration" value="0" /> + </properties> + <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml +]]></output> + </test-case> + </test-suite> + <test-suite type="TestFixture" id="1005" name="LoadSceneAttributeTest" fullname="TestHelper.Editor.LoadSceneAttributeTest" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.089184" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties /> + <test-case id="1006" name="Attach_LoadedSceneNotInBuild" fullname="TestHelper.Editor.LoadSceneAttributeTest.Attach_LoadedSceneNotInBuild" methodname="Attach_LoadedSceneNotInBuild" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" seed="818183097" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.053176" asserts="0"> + <properties> + <property name="retryIteration" value="0" /> + <property name="repeatIteration" value="0" /> + </properties> + <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml +]]></output> + </test-case> + </test-suite> + <test-suite type="TestFixture" id="1007" name="TemporaryBuildScenesUsingInTestTest" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.055223" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> + <properties /> + <test-case id="1008" name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest.GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" methodname="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" seed="1243330322" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.014506" asserts="0"> + <properties> + <property name="retryIteration" value="0" /> + <property name="repeatIteration" value="0" /> + </properties> + <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml +[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report +Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor, +Excluded Assemblies: *.Tests +Included Paths: <Not specified> +Excluded Paths: <Not specified> +Saving results to: /github/workspace/artifacts/editmode-results.xml +]]></output> + </test-case> + </test-suite> + </test-suite> + </test-suite> + </test-suite> + </test-suite> +</test-run> \ No newline at end of file diff --git a/Tests/Editor/TestResources/nunit3.xml.meta b/Tests/Editor/TestResources/nunit3.xml.meta new file mode 100644 index 0000000..acf9a28 --- /dev/null +++ b/Tests/Editor/TestResources/nunit3.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0eaf0c82a5e0b4b4b95f815481936e47 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/RuntimeInternals/CommandLineArgsTest.cs b/Tests/RuntimeInternals/CommandLineArgsTest.cs index 49e3351..f390855 100644 --- a/Tests/RuntimeInternals/CommandLineArgsTest.cs +++ b/Tests/RuntimeInternals/CommandLineArgsTest.cs @@ -39,5 +39,19 @@ public void GetScreenshotDirectory_WithoutArgument_GotDefaultDirectory() var actual = CommandLineArgs.GetScreenshotDirectory(Array.Empty<string>()); Assert.That(actual, Is.EqualTo(Path.Combine(Application.persistentDataPath, "TestHelper", "Screenshots"))); } + + [Test] + public void GetJUnitResultsPath_WithArgument_GotSpecifiedPath() + { + var actual = CommandLineArgs.GetJUnitResultsPath(new[] { "-testHelperJUnitResults", "Test" }); + Assert.That(actual, Is.EqualTo("Test")); + } + + [Test] + public void GetJUnitResultsPath_WithoutArgument_ReturnsNull() + { + var actual = CommandLineArgs.GetJUnitResultsPath(Array.Empty<string>()); + Assert.That(actual, Is.Null); + } } } From 82bb8534970051b8e21f3c1e58c67afdde319a6b Mon Sep 17 00:00:00 2001 From: Koji Hasegawa <hasegawa@hubsys.co.jp> Date: Sun, 27 Oct 2024 19:57:10 +0900 Subject: [PATCH 2/5] Fix fake input NUnit3 XML file format --- Editor/JUnitXmlWriter.cs | 1 + Tests/Editor/JUnitXmlWriterTest.cs | 19 ++++++------------- .../TestDoubles/FakeTestResultAdaptor.cs | 8 +++++++- Tests/Editor/TestResources/nunit3.xml | 1 - 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Editor/JUnitXmlWriter.cs b/Editor/JUnitXmlWriter.cs index 605fa18..ab45363 100644 --- a/Editor/JUnitXmlWriter.cs +++ b/Editor/JUnitXmlWriter.cs @@ -20,6 +20,7 @@ public static void WriteTo(ITestResultAdaptor result, string path) var nunit3XmlStream = new MemoryStream(); var nunit3Writer = XmlWriter.Create(nunit3XmlStream); result.ToXml().WriteTo(nunit3Writer); + nunit3XmlStream.Position = 0; var nunit3Xml = new XPathDocument(nunit3XmlStream); // Create output directory if it does not exist. diff --git a/Tests/Editor/JUnitXmlWriterTest.cs b/Tests/Editor/JUnitXmlWriterTest.cs index b66b957..5fdd6fa 100644 --- a/Tests/Editor/JUnitXmlWriterTest.cs +++ b/Tests/Editor/JUnitXmlWriterTest.cs @@ -11,11 +11,11 @@ namespace TestHelper.Editor public class JUnitXmlWriterTest { private const string TestResourcesPath = "Packages/com.nowsprinting.test-helper/Tests/Editor/TestResources"; - private const string TestOutputDirectoryPath = "Logs/JUnitXmlWriterTest"; + private const string TestOutputDirectoryPath = "Logs/TestHelper/JUnitXmlWriterTest"; // Note: relative path from the project root directory. [Test, Order(0)] - public void WriteTo_DirectoryDoesNotExist_CreateDirectoryAndWriteToFile() + public void WriteTo_CreatedJUnitXmlFormatFile() { if (Directory.Exists(TestOutputDirectoryPath)) { @@ -27,20 +27,13 @@ public void WriteTo_DirectoryDoesNotExist_CreateDirectoryAndWriteToFile() var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); JUnitXmlWriter.WriteTo(result, path); - Assert.That(path, Does.Exist); - } - - [Test] - public void WriteTo_WriteToFile() - { - var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); - var result = new FakeTestResultAdaptor(nunitXmlPath); - var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); - JUnitXmlWriter.WriteTo(result, path); + Assume.That(path, Does.Exist); var actual = File.ReadAllText(path); - var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); + var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); // TODO: use XmlComparer Assert.That(actual, Is.EqualTo(expected)); } + + // TODO: overwrite test } } diff --git a/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs index 2f59f78..6285282 100644 --- a/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs +++ b/Tests/Editor/TestDoubles/FakeTestResultAdaptor.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Xml.Linq; +using NUnit.Framework; using NUnit.Framework.Interfaces; using UnityEditor.TestTools.TestRunner.Api; using TestStatus = UnityEditor.TestTools.TestRunner.Api.TestStatus; @@ -20,7 +22,7 @@ public class FakeTestResultAdaptor : ITestResultAdaptor /// <summary> /// Constructor. /// </summary> - /// <param name="path">NUnit3 XML file path used in <c>ToXml</c> method.</param> + /// <param name="path">Fake input NUnit3 XML file. This file should not contain XML declaration; <c>TNode</c> does not expect it.</param> public FakeTestResultAdaptor(string path) { _path = path; @@ -29,6 +31,10 @@ public FakeTestResultAdaptor(string path) public TNode ToXml() { var xmlText = File.ReadAllText(_path); + var xDocument = XDocument.Parse(xmlText); + Assume.That(xDocument.Declaration, Is.Null, + "The test input file should not contain XML declaration; TNode does not expect this."); + return TNode.FromXml(xmlText); } diff --git a/Tests/Editor/TestResources/nunit3.xml b/Tests/Editor/TestResources/nunit3.xml index fd467eb..0e48b3d 100644 --- a/Tests/Editor/TestResources/nunit3.xml +++ b/Tests/Editor/TestResources/nunit3.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <test-run id="2" testcasecount="3" result="Passed" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.2538825"> <test-suite type="TestSuite" id="1000" name="UnityProject~" fullname="UnityProject~" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.253883" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> <properties> From ac602c976170f969b505bd8d18469a85579ba256 Mon Sep 17 00:00:00 2001 From: Koji Hasegawa <hasegawa@hubsys.co.jp> Date: Mon, 28 Oct 2024 01:51:02 +0900 Subject: [PATCH 3/5] Fix JUnitXmlWriter --- Editor/JUnitXml.meta | 3 + .../JUnitXml/AbstractJUnitElementConverter.cs | 159 ++++++++++++++++++ .../AbstractJUnitElementConverter.cs.meta | 3 + .../JUnitXml/JUnitTestCaseElementConverter.cs | 65 +++++++ .../JUnitTestCaseElementConverter.cs.meta | 3 + .../JUnitTestSuiteElementConverter.cs | 55 ++++++ .../JUnitTestSuiteElementConverter.cs.meta | 3 + .../JUnitTestSuitesElementConverter.cs | 33 ++++ .../JUnitTestSuitesElementConverter.cs.meta | 3 + Editor/JUnitXml/JUnitXmlWriter.cs | 78 +++++++++ Editor/{ => JUnitXml}/JUnitXmlWriter.cs.meta | 0 Editor/JUnitXmlWriter.cs | 42 ----- Editor/TestRunnerCallbacksImpl.cs | 1 + Tests/Editor/JUnitXml.meta | 3 + .../{ => JUnitXml}/JUnitXmlWriterTest.cs | 6 +- .../{ => JUnitXml}/JUnitXmlWriterTest.cs.meta | 0 16 files changed, 412 insertions(+), 45 deletions(-) create mode 100644 Editor/JUnitXml.meta create mode 100644 Editor/JUnitXml/AbstractJUnitElementConverter.cs create mode 100644 Editor/JUnitXml/AbstractJUnitElementConverter.cs.meta create mode 100644 Editor/JUnitXml/JUnitTestCaseElementConverter.cs create mode 100644 Editor/JUnitXml/JUnitTestCaseElementConverter.cs.meta create mode 100644 Editor/JUnitXml/JUnitTestSuiteElementConverter.cs create mode 100644 Editor/JUnitXml/JUnitTestSuiteElementConverter.cs.meta create mode 100644 Editor/JUnitXml/JUnitTestSuitesElementConverter.cs create mode 100644 Editor/JUnitXml/JUnitTestSuitesElementConverter.cs.meta create mode 100644 Editor/JUnitXml/JUnitXmlWriter.cs rename Editor/{ => JUnitXml}/JUnitXmlWriter.cs.meta (100%) delete mode 100644 Editor/JUnitXmlWriter.cs create mode 100644 Tests/Editor/JUnitXml.meta rename Tests/Editor/{ => JUnitXml}/JUnitXmlWriterTest.cs (89%) rename Tests/Editor/{ => JUnitXml}/JUnitXmlWriterTest.cs.meta (100%) diff --git a/Editor/JUnitXml.meta b/Editor/JUnitXml.meta new file mode 100644 index 0000000..d16ad23 --- /dev/null +++ b/Editor/JUnitXml.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 323defc91e624fd7937fe4473c0f009a +timeCreated: 1730050270 \ No newline at end of file diff --git a/Editor/JUnitXml/AbstractJUnitElementConverter.cs b/Editor/JUnitXml/AbstractJUnitElementConverter.cs new file mode 100644 index 0000000..b18b3e8 --- /dev/null +++ b/Editor/JUnitXml/AbstractJUnitElementConverter.cs @@ -0,0 +1,159 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; +using NUnit.Framework.Interfaces; + +namespace TestHelper.Editor.JUnitXml +{ + /// <summary> + /// Abstract class of converting element of NUnit3 test result to JUnit XML format (legacy) element. + /// </summary> + [SuppressMessage("ReSharper", "ConvertToAutoProperty")] + [SuppressMessage("ReSharper", "ConvertToAutoPropertyWhenPossible")] + internal abstract class AbstractJUnitElementConverter + { + // JUnit XML format (legacy) element/attribute names. + protected const string JUnitElementTestsuites = "testsuites"; + protected const string JUnitElementTestsuite = "testsuite"; + protected const string JUnitElementProperties = "properties"; + protected const string JUnitElementProperty = "property"; + protected const string JUnitElementSystemOut = "system-out"; + protected const string JUnitElementTestcase = "testcase"; + protected const string JUnitElementFailure = "failure"; + protected const string JUnitAttributeName = "name"; + protected const string JUnitAttributeDisabled = "disabled"; + protected const string JUnitAttributeErrors = "errors"; + protected const string JUnitAttributeFailures = "failures"; + protected const string JUnitAttributeTests = "tests"; + protected const string JUnitAttributeTime = "time"; + protected const string JUnitAttributeID = "id"; + protected const string JUnitAttributeSkipped = "skipped"; + protected const string JUnitAttributeTimestamp = "timestamp"; + protected const string JUnitAttributeValue = "value"; + protected const string JUnitAttributeClassname = "classname"; + protected const string JUnitAttributeAssertions = "assertions"; + protected const string JUnitAttributeStatus = "status"; + protected const string JUnitAttributeMessage = "message"; + protected const string JUnitAttributeType = "type"; + + // JUnit XML format (legacy) element attributes and elements. + protected string ID => _id; + protected string Name => _name; + protected string ClassName => _classname; + protected int Disabled => _skipped; + protected int Skipped => _skipped; + protected static int Errors => 0; + protected int Failures => _failed + _inconclusive; + protected int Tests => _passed + _failed + _inconclusive + _skipped; + protected int Assertions => _asserts; + protected double Time => _duration; // seconds + protected string Timestamp => _starttime; + protected string Status => _result; + protected bool IsTestCaseSkipped => _result == "Skipped"; // Note: test-case has not skipped attribute + protected List<( string, string)> Properties => _properties; + protected string Reason => _reason; + protected (string, string) Failure => _failure; // message, stack-trace + protected string SystemOut => _output; + + // NUnit3 test result element names. + internal const string NUnitTestRun = "test-run"; + internal const string NUnitTestSuite = "test-suite"; + internal const string NUnitTestCase = "test-case"; + + // NUnit3 test result element attributes and elements. + private readonly string _id; + private readonly string _name; + private readonly string _classname; + private readonly string _result; + private readonly int _passed; + private readonly int _failed; + private readonly int _inconclusive; + private readonly int _skipped; + private readonly int _asserts; + private readonly string _starttime; + private readonly double _duration; // seconds + private readonly List<( string, string)> _properties = new List<(string, string)>(); // name, value + private string _reason; + private (string, string) _failure; // message, stack-trace + private string _output; + + /// <summary> + /// Constructor. + /// Parse NUnit3 test result elements and store them. + /// </summary> + /// <param name="node">NUnit3 test result elements to be converted.</param> + protected AbstractJUnitElementConverter(TNode node) + { + this._id = node.Attributes["id"]; + this._result = node.Attributes["result"]; + this._asserts = System.Convert.ToInt32(node.Attributes["asserts"]); + this._starttime = node.Attributes["start-time"]; + this._duration = System.Convert.ToDouble(node.Attributes["duration"]); + + switch (node.Name) + { + case NUnitTestRun: + case NUnitTestSuite: + this._passed = System.Convert.ToInt32(node.Attributes["passed"]); + this._failed = System.Convert.ToInt32(node.Attributes["failed"]); + this._inconclusive = System.Convert.ToInt32(node.Attributes["inconclusive"]); + this._skipped = System.Convert.ToInt32(node.Attributes["skipped"]); + break; + case NUnitTestCase: + this._passed = 0; + this._failed = 0; + this._inconclusive = 0; + this._skipped = 0; + break; + } + + switch (node.Name) + { + case NUnitTestSuite: + case NUnitTestCase: + this._name = node.Attributes["name"]; + this._classname = node.Attributes["classname"]; + + node.ChildNodes.ForEach(child => + { + if (child.Name == "properties") + { + child.ChildNodes.ForEach(property => + { + this._properties.Add((property.Attributes["name"], property.Attributes["value"])); + }); + } + + if (child.Name == "reason") + { + this._reason = (child.Attributes["message"]); + } + + if (child.Name == "failure") + { + this._failure = (child.Attributes["message"], child.Attributes["stack-trace"]); + } + + if (child.Name == "output") + { + this._output = child.Value; + } + }); + break; + case NUnitTestRun: + this._name = string.Empty; + this._classname = string.Empty; + break; + } + } + + /// <summary> + /// Convert to JUnit XML format (legacy) element. + /// </summary> + /// <returns></returns> + public abstract XElement ToJUnitElement(); + } +} diff --git a/Editor/JUnitXml/AbstractJUnitElementConverter.cs.meta b/Editor/JUnitXml/AbstractJUnitElementConverter.cs.meta new file mode 100644 index 0000000..0c91607 --- /dev/null +++ b/Editor/JUnitXml/AbstractJUnitElementConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 068db053ac1c4d97ba1c1afbbe22c634 +timeCreated: 1730050312 \ No newline at end of file diff --git a/Editor/JUnitXml/JUnitTestCaseElementConverter.cs b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs new file mode 100644 index 0000000..a49579e --- /dev/null +++ b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.Xml.Linq; +using NUnit.Framework.Interfaces; + +namespace TestHelper.Editor.JUnitXml +{ + /// <summary> + /// Converter class of NUnit3 "test-case" element to JUnit "testcase" element. + /// </summary> + internal class JUnitTestCaseElementConverter : AbstractJUnitElementConverter + { + /// <inheritdoc/> + public JUnitTestCaseElementConverter(TNode node) : base(node) + { + } + + /// <inheritdoc/> + public override XElement ToJUnitElement() + { + var element = new XElement(JUnitElementTestcase); + element.Add(new XAttribute(JUnitAttributeName, Name)); + element.Add(new XAttribute(JUnitAttributeClassname, ClassName)); + element.Add(new XAttribute(JUnitAttributeAssertions, Assertions)); + element.Add(new XAttribute(JUnitAttributeTime, Time)); + element.Add(new XAttribute(JUnitAttributeStatus, Status)); + + if (Properties.Count > 0) + { + var propertiesElement = new XElement(JUnitElementProperties); + foreach (var property in Properties) + { + var propertyElement = new XElement(JUnitElementProperty); + propertyElement.Add(new XAttribute(JUnitAttributeName, property.Item1)); + propertyElement.Add(new XAttribute(JUnitAttributeValue, property.Item2)); + propertiesElement.Add(propertyElement); + } + + element.Add(propertiesElement); + } + + if (IsTestCaseSkipped) + { + var skippedNode = new XElement(JUnitAttributeSkipped, Reason); + element.Add(skippedNode); + } + + if (Failures > 0) + { + var failure = new XElement(JUnitElementFailure); + failure.Add(new XAttribute(JUnitAttributeMessage, Failure.Item1)); + failure.Add(new XAttribute(JUnitAttributeType, string.Empty)); + element.Add(failure); + } + + if (string.IsNullOrEmpty(SystemOut)) + { + element.Add(new XElement(JUnitElementSystemOut, SystemOut)); + } + + return element; + } + } +} diff --git a/Editor/JUnitXml/JUnitTestCaseElementConverter.cs.meta b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs.meta new file mode 100644 index 0000000..568374e --- /dev/null +++ b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e3a78e94958143029d6556403c6ec7a0 +timeCreated: 1730050666 \ No newline at end of file diff --git a/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs new file mode 100644 index 0000000..7f19238 --- /dev/null +++ b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.Xml.Linq; +using NUnit.Framework.Interfaces; + +namespace TestHelper.Editor.JUnitXml +{ + /// <summary> + /// Converter class of NUnit3 "test-suite" element to JUnit "testsuite" element. + /// </summary> + internal class JUnitTestSuiteElementConverter : AbstractJUnitElementConverter + { + /// <inheritdoc/> + public JUnitTestSuiteElementConverter(TNode node) : base(node) + { + } + + /// <inheritdoc/> + public override XElement ToJUnitElement() + { + var element = new XElement(JUnitElementTestsuite); + element.Add(new XAttribute(JUnitAttributeName, Name)); + element.Add(new XAttribute(JUnitAttributeTests, Tests)); + element.Add(new XAttribute(JUnitAttributeID, ID)); + element.Add(new XAttribute(JUnitAttributeDisabled, Disabled)); + element.Add(new XAttribute(JUnitAttributeErrors, Errors)); + element.Add(new XAttribute(JUnitAttributeFailures, Failures)); + element.Add(new XAttribute(JUnitAttributeSkipped, Skipped)); + element.Add(new XAttribute(JUnitAttributeTime, Time)); + element.Add(new XAttribute(JUnitAttributeTimestamp, Timestamp)); + + if (Properties.Count > 0) + { + var propertiesElement = new XElement(JUnitElementProperties); + foreach (var property in Properties) + { + var propertyElement = new XElement(JUnitElementProperty); + propertyElement.Add(new XAttribute(JUnitAttributeName, property.Item1)); + propertyElement.Add(new XAttribute(JUnitAttributeValue, property.Item2)); + propertiesElement.Add(propertyElement); + } + + element.Add(propertiesElement); + } + + if (string.IsNullOrEmpty(SystemOut)) + { + element.Add(new XElement(JUnitElementSystemOut, SystemOut)); + } + + return element; + } + } +} diff --git a/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs.meta b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs.meta new file mode 100644 index 0000000..68b5bbb --- /dev/null +++ b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c08508b34cab4243b39056af0a6324bd +timeCreated: 1730050583 \ No newline at end of file diff --git a/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs new file mode 100644 index 0000000..e17c2c8 --- /dev/null +++ b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System.Xml.Linq; +using NUnit.Framework.Interfaces; + +namespace TestHelper.Editor.JUnitXml +{ + /// <summary> + /// Converter class of NUnit3 "test-run" element to JUnit "testsuites" element. + /// </summary> + internal class JUnitTestSuitesElementConverter : AbstractJUnitElementConverter + { + /// <inheritdoc/> + public JUnitTestSuitesElementConverter(TNode node) : base(node) + { + } + + /// <inheritdoc/> + public override XElement ToJUnitElement() + { + var element = new XElement(JUnitElementTestsuites); + element.Add(new XAttribute(JUnitAttributeName, Name)); + element.Add(new XAttribute(JUnitAttributeDisabled, Disabled)); + element.Add(new XAttribute(JUnitAttributeErrors, Errors)); + element.Add(new XAttribute(JUnitAttributeFailures, Failures)); + element.Add(new XAttribute(JUnitAttributeTests, Tests)); + element.Add(new XAttribute(JUnitAttributeTime, Time)); + + return element; + } + } +} diff --git a/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs.meta b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs.meta new file mode 100644 index 0000000..ffb573b --- /dev/null +++ b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: cde60a2b46934cf2a5bf718b6c452ea5 +timeCreated: 1730050521 \ No newline at end of file diff --git a/Editor/JUnitXml/JUnitXmlWriter.cs b/Editor/JUnitXml/JUnitXmlWriter.cs new file mode 100644 index 0000000..0ea9e5f --- /dev/null +++ b/Editor/JUnitXml/JUnitXmlWriter.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2023-2024 Koji Hasegawa. +// This software is released under the MIT License. + +using System; +using System.IO; +using System.Xml; +using System.Xml.Linq; +using NUnit.Framework.Interfaces; +using UnityEditor.TestTools.TestRunner.Api; + +namespace TestHelper.Editor.JUnitXml +{ + /// <summary> + /// Convert NUnit3 test result to JUnit XML format (legacy) and write it to a file. + /// </summary> + /// <remarks> + /// Not supported "Open Test Reporting format" introduced in JUnit 5. + /// </remarks> + /// <seealso href="https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html"/> + /// <seealso href="https://github.com/jenkinsci/benchmark-plugin/blob/master/doc/JUnit%20format/JUnit.txt"/> + public static class JUnitXmlWriter + { + public static void WriteTo(ITestResultAdaptor result, string path) + { + // Convert NUnit3 XML to JUnit XML. + var junitDocument = new XDocument { Declaration = new XDeclaration("1.0", "utf-8", null) }; + var junitRoot = Convert(result.ToXml()); + junitDocument.Add(junitRoot); + + // Create output directory if it does not exist. + var directory = Path.GetDirectoryName(path); + if (directory != null && !Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // Output JUnit XML to the file. + using (var writer = XmlWriter.Create(path)) + { + junitDocument.WriteTo(writer); + writer.Flush(); + } + } + + private static XElement Convert(TNode node) + { + AbstractJUnitElementConverter converter; + switch (node.Name) + { + case AbstractJUnitElementConverter.NUnitTestRun: + converter = new JUnitTestSuitesElementConverter(node); + break; + case AbstractJUnitElementConverter.NUnitTestSuite: + converter = new JUnitTestSuiteElementConverter(node); + break; + case AbstractJUnitElementConverter.NUnitTestCase: + converter = new JUnitTestCaseElementConverter(node); + break; + default: + throw new ArgumentException($"Unsupported node name: {node.Name}"); + } + + var junitElement = converter.ToJUnitElement(); + + // Recursively convert child nodes. + node.ChildNodes.ForEach(child => + { + if (child.Name == AbstractJUnitElementConverter.NUnitTestSuite || + child.Name == AbstractJUnitElementConverter.NUnitTestCase) + { + junitElement.Add(Convert(child)); + } + }); + + return junitElement; + } + } +} diff --git a/Editor/JUnitXmlWriter.cs.meta b/Editor/JUnitXml/JUnitXmlWriter.cs.meta similarity index 100% rename from Editor/JUnitXmlWriter.cs.meta rename to Editor/JUnitXml/JUnitXmlWriter.cs.meta diff --git a/Editor/JUnitXmlWriter.cs b/Editor/JUnitXmlWriter.cs deleted file mode 100644 index ab45363..0000000 --- a/Editor/JUnitXmlWriter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Koji Hasegawa. -// This software is released under the MIT License. - -using System.IO; -using System.Xml; -using System.Xml.XPath; -using System.Xml.Xsl; -using UnityEditor.TestTools.TestRunner.Api; - -namespace TestHelper.Editor -{ - public static class JUnitXmlWriter - { - private const string XsltPath = "Packages/com.nowsprinting.test-helper/Editor/nunit3-junit/nunit3-junit.xslt"; - // Note: This XSLT file is copied from https://github.com/nunit/nunit-transforms/tree/master/nunit3-junit - - public static void WriteTo(ITestResultAdaptor result, string path) - { - // Input - var nunit3XmlStream = new MemoryStream(); - var nunit3Writer = XmlWriter.Create(nunit3XmlStream); - result.ToXml().WriteTo(nunit3Writer); - nunit3XmlStream.Position = 0; - var nunit3Xml = new XPathDocument(nunit3XmlStream); - - // Create output directory if it does not exist. - var directory = Path.GetDirectoryName(path); - if (directory != null && !Directory.Exists(directory)) - { - Directory.CreateDirectory(directory); - } - - // Output (JUnit XML) - var writer = XmlWriter.Create(path); - - // Execute the transformation. - var transformer = new XslCompiledTransform(); - transformer.Load(XsltPath); - transformer.Transform(nunit3Xml, writer); - } - } -} diff --git a/Editor/TestRunnerCallbacksImpl.cs b/Editor/TestRunnerCallbacksImpl.cs index a258882..5009c4e 100644 --- a/Editor/TestRunnerCallbacksImpl.cs +++ b/Editor/TestRunnerCallbacksImpl.cs @@ -1,6 +1,7 @@ // Copyright (c) 2023-2024 Koji Hasegawa. // This software is released under the MIT License. +using TestHelper.Editor.JUnitXml; using TestHelper.RuntimeInternals; using UnityEditor; using UnityEditor.TestTools.TestRunner.Api; diff --git a/Tests/Editor/JUnitXml.meta b/Tests/Editor/JUnitXml.meta new file mode 100644 index 0000000..4ae04d2 --- /dev/null +++ b/Tests/Editor/JUnitXml.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9aedd21ecb7d4a37a4ea7117ed07831a +timeCreated: 1730058413 \ No newline at end of file diff --git a/Tests/Editor/JUnitXmlWriterTest.cs b/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs similarity index 89% rename from Tests/Editor/JUnitXmlWriterTest.cs rename to Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs index 5fdd6fa..b1db9fb 100644 --- a/Tests/Editor/JUnitXmlWriterTest.cs +++ b/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs @@ -5,7 +5,7 @@ using NUnit.Framework; using TestHelper.Editor.TestDoubles; -namespace TestHelper.Editor +namespace TestHelper.Editor.JUnitXml { [TestFixture] public class JUnitXmlWriterTest @@ -25,12 +25,12 @@ public void WriteTo_CreatedJUnitXmlFormatFile() var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); var result = new FakeTestResultAdaptor(nunitXmlPath); var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); - JUnitXmlWriter.WriteTo(result, path); + JUnitXml.JUnitXmlWriter.WriteTo(result, path); Assume.That(path, Does.Exist); var actual = File.ReadAllText(path); - var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); // TODO: use XmlComparer + var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); // TODO: use XmlComparer Assert.That(actual, Is.EqualTo(expected)); } diff --git a/Tests/Editor/JUnitXmlWriterTest.cs.meta b/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs.meta similarity index 100% rename from Tests/Editor/JUnitXmlWriterTest.cs.meta rename to Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs.meta From 47857b187e07392a0b1df9abd04315ef08528b4a Mon Sep 17 00:00:00 2001 From: Koji Hasegawa <hasegawa@hubsys.co.jp> Date: Mon, 28 Oct 2024 05:57:03 +0900 Subject: [PATCH 4/5] Fix tests --- .../JUnitXml/AbstractJUnitElementConverter.cs | 27 +- .../JUnitXml/JUnitTestCaseElementConverter.cs | 16 +- .../JUnitTestSuiteElementConverter.cs | 6 +- .../JUnitTestSuitesElementConverter.cs | 2 +- Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs | 24 +- Tests/Editor/TestResources/junit.xml | 154 +++++++- Tests/Editor/TestResources/nunit3.xml | 339 +++++++++++++++--- 7 files changed, 487 insertions(+), 81 deletions(-) diff --git a/Editor/JUnitXml/AbstractJUnitElementConverter.cs b/Editor/JUnitXml/AbstractJUnitElementConverter.cs index b18b3e8..d22748d 100644 --- a/Editor/JUnitXml/AbstractJUnitElementConverter.cs +++ b/Editor/JUnitXml/AbstractJUnitElementConverter.cs @@ -52,7 +52,9 @@ internal abstract class AbstractJUnitElementConverter protected double Time => _duration; // seconds protected string Timestamp => _starttime; protected string Status => _result; - protected bool IsTestCaseSkipped => _result == "Skipped"; // Note: test-case has not skipped attribute + protected bool IsTestCaseSkipped => _result == "Skipped"; // test-case has not skipped attribute + protected bool IsTestCaseFailed => _result == "Failed"; // test-case has not failures attribute + protected bool IsTestCaseInconclusive => _result == "Inconclusive"; // test-case has not inconclusive attribute protected List<( string, string)> Properties => _properties; protected string Reason => _reason; protected (string, string) Failure => _failure; // message, stack-trace @@ -129,17 +131,34 @@ protected AbstractJUnitElementConverter(TNode node) if (child.Name == "reason") { - this._reason = (child.Attributes["message"]); + child.ChildNodes.ForEach(grandchild => + { + if (grandchild.Name == "message") + { + this._reason = grandchild.Value.Trim(); + } + }); } if (child.Name == "failure") { - this._failure = (child.Attributes["message"], child.Attributes["stack-trace"]); + child.ChildNodes.ForEach(grandchild => + { + switch (grandchild.Name) + { + case "message": + this._failure.Item1 = grandchild.Value.Trim(); + break; + case "stack-trace": + this._failure.Item2 = grandchild.Value.Trim(); + break; + } + }); } if (child.Name == "output") { - this._output = child.Value; + this._output = child.Value.Trim(); } }); break; diff --git a/Editor/JUnitXml/JUnitTestCaseElementConverter.cs b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs index a49579e..23186c6 100644 --- a/Editor/JUnitXml/JUnitTestCaseElementConverter.cs +++ b/Editor/JUnitXml/JUnitTestCaseElementConverter.cs @@ -22,7 +22,7 @@ public override XElement ToJUnitElement() var element = new XElement(JUnitElementTestcase); element.Add(new XAttribute(JUnitAttributeName, Name)); element.Add(new XAttribute(JUnitAttributeClassname, ClassName)); - element.Add(new XAttribute(JUnitAttributeAssertions, Assertions)); + element.Add(new XAttribute(JUnitAttributeAssertions, Assertions)); // always 0 element.Add(new XAttribute(JUnitAttributeTime, Time)); element.Add(new XAttribute(JUnitAttributeStatus, Status)); @@ -46,7 +46,7 @@ public override XElement ToJUnitElement() element.Add(skippedNode); } - if (Failures > 0) + if (IsTestCaseFailed) { var failure = new XElement(JUnitElementFailure); failure.Add(new XAttribute(JUnitAttributeMessage, Failure.Item1)); @@ -54,9 +54,17 @@ public override XElement ToJUnitElement() element.Add(failure); } - if (string.IsNullOrEmpty(SystemOut)) + if (IsTestCaseInconclusive) { - element.Add(new XElement(JUnitElementSystemOut, SystemOut)); + var failure = new XElement(JUnitElementFailure); + failure.Add(new XAttribute(JUnitAttributeMessage, Reason)); + failure.Add(new XAttribute(JUnitAttributeType, string.Empty)); + element.Add(failure); + } + + if (!string.IsNullOrEmpty(SystemOut)) + { + element.Add(new XElement(JUnitElementSystemOut, new XCData(SystemOut))); } return element; diff --git a/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs index 7f19238..de3a9d5 100644 --- a/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs +++ b/Editor/JUnitXml/JUnitTestSuiteElementConverter.cs @@ -24,7 +24,7 @@ public override XElement ToJUnitElement() element.Add(new XAttribute(JUnitAttributeTests, Tests)); element.Add(new XAttribute(JUnitAttributeID, ID)); element.Add(new XAttribute(JUnitAttributeDisabled, Disabled)); - element.Add(new XAttribute(JUnitAttributeErrors, Errors)); + element.Add(new XAttribute(JUnitAttributeErrors, Errors)); // always 0 element.Add(new XAttribute(JUnitAttributeFailures, Failures)); element.Add(new XAttribute(JUnitAttributeSkipped, Skipped)); element.Add(new XAttribute(JUnitAttributeTime, Time)); @@ -44,9 +44,9 @@ public override XElement ToJUnitElement() element.Add(propertiesElement); } - if (string.IsNullOrEmpty(SystemOut)) + if (!string.IsNullOrEmpty(SystemOut)) { - element.Add(new XElement(JUnitElementSystemOut, SystemOut)); + element.Add(new XElement(JUnitElementSystemOut, new XCData(SystemOut))); } return element; diff --git a/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs index e17c2c8..c8e42b2 100644 --- a/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs +++ b/Editor/JUnitXml/JUnitTestSuitesElementConverter.cs @@ -22,7 +22,7 @@ public override XElement ToJUnitElement() var element = new XElement(JUnitElementTestsuites); element.Add(new XAttribute(JUnitAttributeName, Name)); element.Add(new XAttribute(JUnitAttributeDisabled, Disabled)); - element.Add(new XAttribute(JUnitAttributeErrors, Errors)); + element.Add(new XAttribute(JUnitAttributeErrors, Errors)); // always 0 element.Add(new XAttribute(JUnitAttributeFailures, Failures)); element.Add(new XAttribute(JUnitAttributeTests, Tests)); element.Add(new XAttribute(JUnitAttributeTime, Time)); diff --git a/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs b/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs index b1db9fb..d614de4 100644 --- a/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs +++ b/Tests/Editor/JUnitXml/JUnitXmlWriterTest.cs @@ -3,6 +3,7 @@ using System.IO; using NUnit.Framework; +using TestHelper.Comparers; using TestHelper.Editor.TestDoubles; namespace TestHelper.Editor.JUnitXml @@ -25,15 +26,30 @@ public void WriteTo_CreatedJUnitXmlFormatFile() var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); var result = new FakeTestResultAdaptor(nunitXmlPath); var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); - JUnitXml.JUnitXmlWriter.WriteTo(result, path); + JUnitXmlWriter.WriteTo(result, path); Assume.That(path, Does.Exist); var actual = File.ReadAllText(path); - var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); // TODO: use XmlComparer - Assert.That(actual, Is.EqualTo(expected)); + var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); + Assert.That(actual, Is.EqualTo(expected).Using(new XmlComparer())); } - // TODO: overwrite test + [Test] + public void WriteTo_ExistFile_OverwriteFile() + { + var nunitXmlPath = Path.Combine(TestResourcesPath, "nunit3.xml"); + var result = new FakeTestResultAdaptor(nunitXmlPath); + var path = Path.Combine(TestOutputDirectoryPath, TestContext.CurrentContext.Test.Name + ".xml"); + + // Destroy the output destination file. + File.Copy(nunitXmlPath, path, true); + + JUnitXmlWriter.WriteTo(result, path); + + var actual = File.ReadAllText(path); + var expected = File.ReadAllText(Path.Combine(TestResourcesPath, "junit.xml")); + Assert.That(actual, Is.EqualTo(expected).Using(new XmlComparer())); + } } } diff --git a/Tests/Editor/TestResources/junit.xml b/Tests/Editor/TestResources/junit.xml index 5749df1..099508d 100644 --- a/Tests/Editor/TestResources/junit.xml +++ b/Tests/Editor/TestResources/junit.xml @@ -1,10 +1,144 @@ -<?xml version="1.0" encoding="utf-8"?><testsuites tests="3" failures="0" disabled="0" time="0.2538825"><testsuite tests="1" time="0.092622" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_CreateNewSceneWithCameraAndLight" assertions="0" time="0.036802" status="Passed" classname="TestHelper.Editor.CreateSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml -</system-out></testcase></testsuite><testsuite tests="1" time="0.089184" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="Attach_LoadedSceneNotInBuild" assertions="0" time="0.053176" status="Passed" classname="TestHelper.Editor.LoadSceneAttributeTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml -</system-out></testcase></testsuite><testsuite tests="1" time="0.055223" errors="0" failures="0" skipped="0" timestamp="2024-05-03 10:09:28Z" name="UnityProject~.TestHelper.Editor."><testcase name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" assertions="0" time="0.014506" status="Passed" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest"><system-out>[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml -[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report -Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor, -Excluded Assemblies: *.Tests -Included Paths: <Not specified> -Excluded Paths: <Not specified> -Saving results to: /github/workspace/artifacts/editmode-results.xml -</system-out></testcase></testsuite></testsuites> \ No newline at end of file +<?xml version="1.0" encoding="utf-8"?> +<testsuites name="" disabled="1" errors="0" failures="7" tests="11" time="0.0876691"> + <testsuite name="NUnitXml" tests="11" id="1031" disabled="1" errors="0" failures="7" skipped="1" time="0.087669" + timestamp="2024-10-27 20:25:09Z"> + <properties> + <property name="platform" value="EditMode"/> + </properties> + <testsuite name="MyFeature1.Editor.Tests.dll" tests="10" id="1018" disabled="1" errors="0" failures="6" + skipped="1" time="0.072287" timestamp="2024-10-27 20:25:09Z"> + <properties> + <property name="_PID" value="19711"/> + <property name="_APPDOMAIN" value="Unity Child Domain"/> + <property name="platform" value="EditMode"/> + <property name="EditorOnly" value="True"/> + </properties> + <testsuite name="MyFeature1" tests="10" id="1019" disabled="1" errors="0" failures="6" skipped="1" + time="0.071332" timestamp="2024-10-27 20:25:09Z"> + <testsuite name="Editor" tests="10" id="1020" disabled="1" errors="0" failures="6" skipped="1" + time="0.07093" timestamp="2024-10-27 20:25:09Z"> + <testsuite name="MyFeature1Test" tests="7" id="1005" disabled="1" errors="0" failures="5" + skipped="1" time="0.046854" timestamp="2024-10-27 20:25:09Z"> + <testcase name="TestFailedByAssertion" classname="MyFeature1.Editor.MyFeature1Test" + assertions="0" time="0.010554" status="Failed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure message="failed" type=""/> + </testcase> + <testcase name="TestFailedByException" classname="MyFeature1.Editor.MyFeature1Test" + assertions="0" time="0.001907" status="Failed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure message="System.ApplicationException : Fail test by exception" type=""/> + </testcase> + <testcase name="TestFailedByLogError" classname="MyFeature1.Editor.MyFeature1Test" + assertions="0" time="0.004971" status="Failed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure + message="Unhandled log message: '[Error] Fail test by log error'. Use UnityEngine.TestTools.LogAssert.Expect" + type=""/> + <system-out><![CDATA[Fail test by log error]]></system-out> + </testcase> + <testcase name="TestFailedByUnityEngineAssertion" classname="MyFeature1.Editor.MyFeature1Test" + assertions="0" time="0.000755" status="Failed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure + message="UnityEngine.Assertions.AssertionException : Fail test by UnityEngine.Assertions.Assert
Assertion failure. Value was False
Expected: True" + type=""/> + </testcase> + <testcase name="TestInconclusive" classname="MyFeature1.Editor.MyFeature1Test" assertions="0" + time="0.006531" status="Inconclusive"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure message="inconclusive
 Expected: False
 But was: True" type=""/> + </testcase> + <testcase name="TestPassed" classname="MyFeature1.Editor.MyFeature1Test" assertions="0" + time="0.002809" status="Passed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <system-out><![CDATA[passed]]></system-out> + </testcase> + <testcase name="TestSkipped" classname="MyFeature1.Editor.MyFeature1Test" assertions="0" + time="0.000242" status="Skipped"> + <properties> + <property name="_SKIPREASON" value="Skipped test"/> + </properties> + <skipped>Skipped test</skipped> + </testcase> + </testsuite> + <testsuite name="MyFeature1TestParameterized" tests="3" id="1013" disabled="0" errors="0" + failures="1" skipped="0" time="0.02234" timestamp="2024-10-27 20:25:09Z"> + <testsuite name="Parameterized" tests="3" id="1017" disabled="0" errors="0" failures="1" + skipped="0" time="0.009447" timestamp="2024-10-27 20:25:09Z"> + <testcase name="Parameterized(1,2,3)" + classname="MyFeature1.Editor.MyFeature1TestParameterized" assertions="0" + time="0.00569" status="Passed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + </testcase> + <testcase name="Parameterized(2,3,5)" + classname="MyFeature1.Editor.MyFeature1TestParameterized" assertions="0" + time="9.8E-05" status="Passed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + </testcase> + <testcase name="Parameterized(3,4,9)" + classname="MyFeature1.Editor.MyFeature1TestParameterized" assertions="0" + time="0.001034" status="Failed"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure message="Expected: 9
 But was: 7" type=""/> + </testcase> + </testsuite> + </testsuite> + </testsuite> + </testsuite> + </testsuite> + <testsuite name="MyFeature2.Editor.Tests.dll" tests="1" id="1023" disabled="0" errors="0" failures="1" + skipped="0" time="0.005488" timestamp="2024-10-27 20:25:09Z"> + <properties> + <property name="_PID" value="19711"/> + <property name="_APPDOMAIN" value="Unity Child Domain"/> + <property name="platform" value="EditMode"/> + <property name="EditorOnly" value="True"/> + </properties> + <testsuite name="MyFeature2" tests="1" id="1024" disabled="0" errors="0" failures="1" skipped="0" + time="0.002666" timestamp="2024-10-27 20:25:09Z"> + <testsuite name="Editor" tests="1" id="1025" disabled="0" errors="0" failures="1" skipped="0" + time="0.002034" timestamp="2024-10-27 20:25:09Z"> + <testsuite name="MyFeature2Test" tests="1" id="1021" disabled="0" errors="0" failures="1" + skipped="0" time="0.00151" timestamp="2024-10-27 20:25:09Z"> + <testcase name="TestInconclusive" classname="MyFeature2.Editor.MyFeature2Test" assertions="0" + time="0.000408" status="Inconclusive"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure message="inconclusive
 Expected: False
 But was: True" type=""/> + </testcase> + </testsuite> + </testsuite> + </testsuite> + </testsuite> + </testsuite> +</testsuites> \ No newline at end of file diff --git a/Tests/Editor/TestResources/nunit3.xml b/Tests/Editor/TestResources/nunit3.xml index 0e48b3d..c64b0b7 100644 --- a/Tests/Editor/TestResources/nunit3.xml +++ b/Tests/Editor/TestResources/nunit3.xml @@ -1,60 +1,289 @@ -<test-run id="2" testcasecount="3" result="Passed" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.2538825"> - <test-suite type="TestSuite" id="1000" name="UnityProject~" fullname="UnityProject~" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.253883" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties> - <property name="platform" value="EditMode" /> - </properties> - <test-suite type="Assembly" id="1009" name="TestHelper.Editor.Tests.dll" fullname="/github/workspace/UnityProject~/Library/ScriptAssemblies/TestHelper.Editor.Tests.dll" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.246003" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties> - <property name="_PID" value="3301" /> - <property name="_APPDOMAIN" value="Unity Child Domain" /> - <property name="platform" value="EditMode" /> - <property name="EditorOnly" value="True" /> - </properties> - <test-suite type="TestSuite" id="1010" name="TestHelper" fullname="TestHelper" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.245391" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties /> - <test-suite type="TestSuite" id="1011" name="Editor" fullname="TestHelper.Editor" runstate="Runnable" testcasecount="3" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.241916" total="3" passed="3" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties /> - <test-suite type="TestFixture" id="1003" name="CreateSceneAttributeTest" fullname="TestHelper.Editor.CreateSceneAttributeTest" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.092622" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties /> - <test-case id="1004" name="Attach_CreateNewSceneWithCameraAndLight" fullname="TestHelper.Editor.CreateSceneAttributeTest.Attach_CreateNewSceneWithCameraAndLight" methodname="Attach_CreateNewSceneWithCameraAndLight" classname="TestHelper.Editor.CreateSceneAttributeTest" runstate="Runnable" seed="1087926775" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.036802" asserts="0"> - <properties> - <property name="retryIteration" value="0" /> - <property name="repeatIteration" value="0" /> - </properties> - <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0001.xml +<test-run id="2" testcasecount="11" result="Failed(Child)" total="11" passed="3" failed="5" inconclusive="2" skipped="1" + asserts="0" engine-version="3.5.0.0" clr-version="4.0.30319.42000" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.0876691"> + <test-suite type="TestSuite" id="1031" name="NUnitXml" fullname="NUnitXml" runstate="Runnable" testcasecount="11" + result="Failed" site="Child" start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" + duration="0.087669" total="11" passed="3" failed="5" inconclusive="2" skipped="1" asserts="0"> + <properties> + <property name="platform" value="EditMode"/> + </properties> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-suite type="Assembly" id="1018" name="MyFeature1.Editor.Tests.dll" + fullname="/Users/ko2hase/Documents/NUnitXml/Library/ScriptAssemblies/MyFeature1.Editor.Tests.dll" + runstate="Runnable" testcasecount="10" result="Failed" site="Child" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.072287" total="10" + passed="3" failed="5" inconclusive="1" skipped="1" asserts="0"> + <properties> + <property name="_PID" value="19711"/> + <property name="_APPDOMAIN" value="Unity Child Domain"/> + <property name="platform" value="EditMode"/> + <property name="EditorOnly" value="True"/> + </properties> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-suite type="TestSuite" id="1019" name="MyFeature1" fullname="MyFeature1" runstate="Runnable" + testcasecount="10" result="Failed" site="Child" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.071332" total="10" passed="3" failed="5" + inconclusive="1" skipped="1" asserts="0"> + <properties/> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-suite type="TestSuite" id="1020" name="Editor" fullname="MyFeature1.Editor" runstate="Runnable" + testcasecount="10" result="Failed" site="Child" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.070930" total="10" passed="3" failed="5" + inconclusive="1" skipped="1" asserts="0"> + <properties/> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-suite type="TestFixture" id="1005" name="MyFeature1Test" + fullname="MyFeature1.Editor.MyFeature1Test" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" testcasecount="7" result="Failed" site="Child" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.046854" + total="7" passed="1" failed="4" inconclusive="1" skipped="1" asserts="0"> + <properties/> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-case id="1007" name="TestFailedByAssertion" + fullname="MyFeature1.Editor.MyFeature1Test.TestFailedByAssertion" + methodname="TestFailedByAssertion" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" seed="1040389132" result="Failed" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.010554" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure> + <message><![CDATA[failed]]></message> + <stack-trace><![CDATA[at MyFeature1.Editor.MyFeature1Test.TestFailedByAssertion () [0x00001] in /Users/ko2hase/Documents/NUnitXml/Assets/MyFeature1/Tests/Editor/MyFeature1Test.cs:25 +]]></stack-trace> + </failure> + </test-case> + <test-case id="1008" name="TestFailedByException" + fullname="MyFeature1.Editor.MyFeature1Test.TestFailedByException" + methodname="TestFailedByException" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" seed="2056623004" result="Failed" label="Error" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.001907" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure> + <message><![CDATA[System.ApplicationException : Fail test by exception]]></message> + <stack-trace><![CDATA[ at MyFeature1.Editor.MyFeature1Test.TestFailedByException () [0x00001] in /Users/ko2hase/Documents/NUnitXml/Assets/MyFeature1/Tests/Editor/MyFeature1Test.cs:31 + at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&) + at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <b904252b6b4e4277834bcca7e51f318d>:0 ]]></stack-trace> + </failure> + </test-case> + <test-case id="1009" name="TestFailedByLogError" + fullname="MyFeature1.Editor.MyFeature1Test.TestFailedByLogError" + methodname="TestFailedByLogError" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" seed="810015734" result="Failed" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.004971" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure> + <message> + <![CDATA[Unhandled log message: '[Error] Fail test by log error'. Use UnityEngine.TestTools.LogAssert.Expect]]></message> + <stack-trace><![CDATA[MyFeature1.Editor.MyFeature1Test:TestFailedByLogError () (at Assets/MyFeature1/Tests/Editor/MyFeature1Test.cs:37) +System.Reflection.MethodBase:Invoke (object,object[]) +NUnit.Framework.Internal.Reflect:InvokeMethod (System.Reflection.MethodInfo,object,object[]) +NUnit.Framework.Internal.MethodWrapper:Invoke (object,object[]) +NUnit.Framework.Internal.Commands.TestMethodCommand:RunNonAsyncTestMethod (NUnit.Framework.Internal.ITestExecutionContext) +NUnit.Framework.Internal.Commands.TestMethodCommand:RunTestMethod (NUnit.Framework.Internal.ITestExecutionContext) +NUnit.Framework.Internal.Commands.TestMethodCommand:Execute (NUnit.Framework.Internal.ITestExecutionContext) +UnityEditor.EditorApplication:Internal_CallUpdateFunctions () (at /Users/bokken/build/output/unity/unity/Editor/Mono/EditorApplication.cs:381) + +]]></stack-trace> + </failure> + <output><![CDATA[Fail test by log error ]]></output> - </test-case> - </test-suite> - <test-suite type="TestFixture" id="1005" name="LoadSceneAttributeTest" fullname="TestHelper.Editor.LoadSceneAttributeTest" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.089184" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties /> - <test-case id="1006" name="Attach_LoadedSceneNotInBuild" fullname="TestHelper.Editor.LoadSceneAttributeTest.Attach_LoadedSceneNotInBuild" methodname="Attach_LoadedSceneNotInBuild" classname="TestHelper.Editor.LoadSceneAttributeTest" runstate="Runnable" seed="818183097" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.053176" asserts="0"> - <properties> - <property name="retryIteration" value="0" /> - <property name="repeatIteration" value="0" /> - </properties> - <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0002.xml + </test-case> + <test-case id="1010" name="TestFailedByUnityEngineAssertion" + fullname="MyFeature1.Editor.MyFeature1Test.TestFailedByUnityEngineAssertion" + methodname="TestFailedByUnityEngineAssertion" + classname="MyFeature1.Editor.MyFeature1Test" runstate="Runnable" seed="2040230563" + result="Failed" label="Error" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.000755" asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure> + <message><![CDATA[UnityEngine.Assertions.AssertionException : Fail test by UnityEngine.Assertions.Assert +Assertion failure. Value was False +Expected: True]]></message> + <stack-trace><![CDATA[ at UnityEngine.Assertions.Assert.Fail (System.String message, System.String userMessage) [0x0003c] in /Users/bokken/build/output/unity/unity/Runtime/Export/Assertions/Assert/AssertBase.cs:29 + at UnityEngine.Assertions.Assert.IsTrue (System.Boolean condition, System.String message) [0x00009] in /Users/bokken/build/output/unity/unity/Runtime/Export/Assertions/Assert/AssertBool.cs:20 + at MyFeature1.Editor.MyFeature1Test.TestFailedByUnityEngineAssertion () [0x00001] in /Users/ko2hase/Documents/NUnitXml/Assets/MyFeature1/Tests/Editor/MyFeature1Test.cs:43 + at (wrapper managed-to-native) System.Reflection.RuntimeMethodInfo.InternalInvoke(System.Reflection.RuntimeMethodInfo,object,object[],System.Exception&) + at System.Reflection.RuntimeMethodInfo.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0006a] in <b904252b6b4e4277834bcca7e51f318d>:0 ]]></stack-trace> + </failure> + </test-case> + <test-case id="1011" name="TestInconclusive" + fullname="MyFeature1.Editor.MyFeature1Test.TestInconclusive" + methodname="TestInconclusive" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" seed="1689148236" result="Inconclusive" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.006531" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <reason> + <message><![CDATA[ inconclusive + Expected: False + But was: True +]]></message> + </reason> + </test-case> + <test-case id="1006" name="TestPassed" fullname="MyFeature1.Editor.MyFeature1Test.TestPassed" + methodname="TestPassed" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Runnable" seed="694536572" result="Passed" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.002809" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <output><![CDATA[passed ]]></output> - </test-case> - </test-suite> - <test-suite type="TestFixture" id="1007" name="TemporaryBuildScenesUsingInTestTest" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.055223" total="1" passed="1" failed="0" inconclusive="0" skipped="0" asserts="0"> - <properties /> - <test-case id="1008" name="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" fullname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest.GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" methodname="GetScenesUsingInTest_AttachedToMethod_ReturnScenesSpecifiedByAttribute" classname="TestHelper.Editor.TemporaryBuildScenesUsingInTestTest" runstate="Runnable" seed="1243330322" result="Passed" start-time="2024-05-03 10:09:28Z" end-time="2024-05-03 10:09:28Z" duration="0.014506" asserts="0"> - <properties> - <property name="retryIteration" value="0" /> - <property name="repeatIteration" value="0" /> - </properties> - <output><![CDATA[[Code Coverage] Code Coverage results for visited sequence points were saved in /github/workspace/CodeCoverage/UnityProject~-opencov/EditMode/TestCoverageResults_0003.xml -[Code Coverage] Code Coverage Report was generated in /github/workspace/CodeCoverage/Report -Included Assemblies: TestHelper,TestHelper.RuntimeInternals,TestHelper.Editor, -Excluded Assemblies: *.Tests -Included Paths: <Not specified> -Excluded Paths: <Not specified> -Saving results to: /github/workspace/artifacts/editmode-results.xml -]]></output> - </test-case> - </test-suite> + </test-case> + <test-case id="1012" name="TestSkipped" fullname="MyFeature1.Editor.MyFeature1Test.TestSkipped" + methodname="TestSkipped" classname="MyFeature1.Editor.MyFeature1Test" + runstate="Ignored" seed="2113885182" result="Skipped" label="Ignored" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.000242" + asserts="0"> + <properties> + <property name="_SKIPREASON" value="Skipped test"/> + </properties> + <reason> + <message><![CDATA[Skipped test]]></message> + </reason> + </test-case> + </test-suite> + <test-suite type="TestFixture" id="1013" name="MyFeature1TestParameterized" + fullname="MyFeature1.Editor.MyFeature1TestParameterized" + classname="MyFeature1.Editor.MyFeature1TestParameterized" runstate="Runnable" + testcasecount="3" result="Failed" site="Child" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.022340" total="3" passed="2" failed="1" + inconclusive="0" skipped="0" asserts="0"> + <properties/> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-suite type="ParameterizedMethod" id="1017" name="Parameterized" + fullname="MyFeature1.Editor.MyFeature1TestParameterized.Parameterized" + classname="MyFeature1.Editor.MyFeature1TestParameterized" runstate="Runnable" + testcasecount="3" result="Failed" site="Child" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.009447" total="3" passed="2" failed="1" + inconclusive="0" skipped="0" asserts="0"> + <properties/> + <failure> + <message><![CDATA[One or more child tests had errors]]></message> + </failure> + <test-case id="1014" name="Parameterized(1,2,3)" + fullname="MyFeature1.Editor.MyFeature1TestParameterized.Parameterized(1,2,3)" + methodname="Parameterized" + classname="MyFeature1.Editor.MyFeature1TestParameterized" runstate="Runnable" + seed="470631257" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.005690" asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + </test-case> + <test-case id="1015" name="Parameterized(2,3,5)" + fullname="MyFeature1.Editor.MyFeature1TestParameterized.Parameterized(2,3,5)" + methodname="Parameterized" + classname="MyFeature1.Editor.MyFeature1TestParameterized" runstate="Runnable" + seed="2138031648" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.000098" asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + </test-case> + <test-case id="1016" name="Parameterized(3,4,9)" + fullname="MyFeature1.Editor.MyFeature1TestParameterized.Parameterized(3,4,9)" + methodname="Parameterized" + classname="MyFeature1.Editor.MyFeature1TestParameterized" runstate="Runnable" + seed="1211309338" result="Failed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.001034" asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <failure> + <message><![CDATA[ Expected: 9 + But was: 7 +]]></message> + <stack-trace><![CDATA[at MyFeature1.Editor.MyFeature1TestParameterized.Parameterized (System.Int32 i, System.Int32 j, System.Int32 expected) [0x00001] in /Users/ko2hase/Documents/NUnitXml/Assets/MyFeature1/Tests/Editor/MyFeature1TestParameterized.cs:16 +]]></stack-trace> + </failure> + </test-case> + </test-suite> + </test-suite> + </test-suite> + </test-suite> + </test-suite> + <test-suite type="Assembly" id="1023" name="MyFeature2.Editor.Tests.dll" + fullname="/Users/ko2hase/Documents/NUnitXml/Library/ScriptAssemblies/MyFeature2.Editor.Tests.dll" + runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.005488" total="1" passed="0" failed="0" inconclusive="1" + skipped="0" asserts="0"> + <properties> + <property name="_PID" value="19711"/> + <property name="_APPDOMAIN" value="Unity Child Domain"/> + <property name="platform" value="EditMode"/> + <property name="EditorOnly" value="True"/> + </properties> + <test-suite type="TestSuite" id="1024" name="MyFeature2" fullname="MyFeature2" runstate="Runnable" + testcasecount="1" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.002666" total="1" passed="0" failed="0" + inconclusive="1" skipped="0" asserts="0"> + <properties/> + <test-suite type="TestSuite" id="1025" name="Editor" fullname="MyFeature2.Editor" runstate="Runnable" + testcasecount="1" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.002034" total="1" passed="0" failed="0" + inconclusive="1" skipped="0" asserts="0"> + <properties/> + <test-suite type="TestFixture" id="1021" name="MyFeature2Test" + fullname="MyFeature2.Editor.MyFeature2Test" classname="MyFeature2.Editor.MyFeature2Test" + runstate="Runnable" testcasecount="1" result="Passed" start-time="2024-10-27 20:25:09Z" + end-time="2024-10-27 20:25:09Z" duration="0.001510" total="1" passed="0" failed="0" + inconclusive="1" skipped="0" asserts="0"> + <properties/> + <test-case id="1022" name="TestInconclusive" + fullname="MyFeature2.Editor.MyFeature2Test.TestInconclusive" + methodname="TestInconclusive" classname="MyFeature2.Editor.MyFeature2Test" + runstate="Runnable" seed="350965709" result="Inconclusive" + start-time="2024-10-27 20:25:09Z" end-time="2024-10-27 20:25:09Z" duration="0.000408" + asserts="0"> + <properties> + <property name="retryIteration" value="0"/> + <property name="repeatIteration" value="0"/> + </properties> + <reason> + <message><![CDATA[ inconclusive + Expected: False + But was: True +]]></message> + </reason> + </test-case> + </test-suite> + </test-suite> + </test-suite> </test-suite> - </test-suite> </test-suite> - </test-suite> </test-run> \ No newline at end of file From af2f20b2399d19114a299d92d0b90be66af14bfa Mon Sep 17 00:00:00 2001 From: Koji Hasegawa <hasegawa@hubsys.co.jp> Date: Mon, 28 Oct 2024 08:04:49 +0900 Subject: [PATCH 5/5] Mod README.md --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e4e0edd..d2a44b0 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,7 @@ public class MyTestClass } ``` -> [!NOTE] +> [!NOTE] > - Scene file path is starts with `Assets/` or `Packages/` or `.`. And package name using `name` instead of `displayName`, when scenes in the package. (e.g., `Packages/com.nowsprinting.test-helper/Tests/Scenes/Scene.unity`) @@ -529,11 +529,11 @@ public class MyTestClass > When used with operators, use it in method style. e.g., `Is.Not.Destroyed()` -### RuntimeInternals +### Runtime APIs `TestHelper.RuntimeInternals` assembly can be used from the runtime code because it does not depend on test-framework. -> [!NOTE] +> [!NOTE] > The "Define Constraints" is set to `UNITY_INCLUDE_TESTS || COM_NOWSPRINTING_TEST_HELPER_ENABLE` in this assembly definition files, so it is generally excluded from release builds. > To use the feature in release builds, add `COM_NOWSPRINTING_TEST_HELPER_ENABLE` to the "Define Symbols" at build time. @@ -615,7 +615,7 @@ public class MyTestClass } ``` -> [!NOTE] +> [!NOTE] > - Scene file path is starts with `Assets/` or `Packages/` or `.`. And package name using `name` instead of `displayName`, when scenes in the package. (e.g., `Packages/com.nowsprinting.test-helper/Tests/Scenes/Scene.unity`) > - When loading the scene that is not in "Scenes in Build", use [BuildSceneAttribute](#BuildScene). @@ -624,7 +624,15 @@ public class MyTestClass #### Open Persistent Data Directory -Select **Window** > **Open Persistent Data Directory**, which opens the directory pointed to by [Application.persistentDataPath](https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html) in the Finder/ File Explorer. +Select **Window > Open Persistent Data Directory**, which opens the directory pointed to by [Application.persistentDataPath](https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html) in the Finder/ File Explorer. + + +### JUnit XML format report + +If you specify path with `-testHelperJUnitResults` command line option, the test result will be written in JUnit XML format when the tests are finished. + +> [!NOTE] +> The JUnit XML format is the so-called "Legacy." It does not support the "Open Test Reporting format" introduced in JUnit 5.