diff --git a/.github/agents/requirements-agent.md b/.github/agents/requirements-agent.md index c96b463..c33ad5e 100644 --- a/.github/agents/requirements-agent.md +++ b/.github/agents/requirements-agent.md @@ -36,6 +36,20 @@ Invoke the requirements-agent for: - Implementation validation beyond requirement scope - **Unit tests**: For library functionality and internal component behavior +### Test Source Filters + +Test links can include a source filter prefix to restrict which test results count as evidence. This is essential +for platform and framework requirements - **never remove these filters**. + +- `windows@TestName` - proves the test passed on a Windows platform +- `ubuntu@TestName` - proves the test passed on a Linux (Ubuntu) platform +- `net8.0@TestName` - proves the test passed under the .NET 8 runtime +- `net9.0@TestName` - proves the test passed under the .NET 9 runtime +- `net10.0@TestName` - proves the test passed under the .NET 10 runtime + +Without the source filter, any matching test result satisfies the requirement regardless of which platform or +framework produced it. Removing a filter invalidates the evidence for platform/framework requirements. + ### Requirements Format Follow the `requirements.yaml` structure: diff --git a/.github/agents/test-developer.md b/.github/agents/test-developer.md index bac917c..fda2b4e 100644 --- a/.github/agents/test-developer.md +++ b/.github/agents/test-developer.md @@ -63,12 +63,61 @@ public void ClassName_MethodUnderTest_Scenario_ExpectedBehavior() - Failure-testing and error handling scenarios - Verifying internal behavior beyond requirement scope +### Test Source Filters + +Test links in `requirements.yaml` can include a source filter prefix to restrict which test results count as +evidence. These filters are critical for platform and framework requirements - **do not remove them**. + +- `windows@TestName` - proves the test passed on a Windows platform +- `ubuntu@TestName` - proves the test passed on a Linux (Ubuntu) platform +- `net8.0@TestName` - proves the test passed under the .NET 8 runtime +- `net9.0@TestName` - proves the test passed under the .NET 9 runtime +- `net10.0@TestName` - proves the test passed under the .NET 10 runtime + +Removing a source filter means a test result from any environment can satisfy the requirement, which invalidates +the evidence-based proof that the library works on a specific platform or framework. + ### TestResults-Specific - Unit tests live in `test/` directory - Use MSTest V4 testing framework - Follow existing naming conventions in the test suite +### MSTest V4 Best Practices + +Common anti-patterns to avoid (not exhaustive): + +1. **Avoid Assertions in Catch Blocks (MSTEST0058)** - Instead of wrapping code in try/catch and asserting in the + catch block, use `Assert.ThrowsExactly()`: + + ```csharp + var ex = Assert.ThrowsExactly(() => SomeWork()); + Assert.Contains("Some message", ex.Message); + ``` + +2. **Avoid using Assert.IsTrue / Assert.IsFalse for equality checks** - Use `Assert.AreEqual` / + `Assert.AreNotEqual` instead, as it provides better failure messages: + + ```csharp + // ❌ Bad: Assert.IsTrue(result == expected); + // ✅ Good: Assert.AreEqual(expected, result); + ``` + +3. **Avoid non-public test classes and methods** - Test classes and `[TestMethod]` methods must be `public` or + they will be silently ignored: + + ```csharp + // ❌ Bad: internal class MyTests + // ✅ Good: public class MyTests + ``` + +4. **Avoid Assert.IsTrue(collection.Count == N)** - Use `Assert.HasCount` for count assertions: + + ```csharp + // ❌ Bad: Assert.IsTrue(collection.Count == 3); + // ✅ Good: Assert.HasCount(3, collection); + ``` + ## Defer To - **Requirements Agent**: For test strategy and coverage requirements diff --git a/AGENTS.md b/AGENTS.md index 037abed..669a960 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -273,6 +273,20 @@ The build process generates: - **Requirements Specification PDF**: Complete requirements document - **Trace Matrix PDF**: Shows mapping between requirements and test cases with pass/fail status +## Test Source Filters + +Test links in `requirements.yaml` can include a source filter prefix to restrict which test results count as +evidence. This is critical for platform and framework requirements - **do not remove these filters**. + +- `windows@TestName` - proves the test passed on a Windows platform +- `ubuntu@TestName` - proves the test passed on a Linux (Ubuntu) platform +- `net8.0@TestName` - proves the test passed under the .NET 8 runtime +- `net9.0@TestName` - proves the test passed under the .NET 9 runtime +- `net10.0@TestName` - proves the test passed under the .NET 10 runtime + +Without the source filter, a test result from any platform/framework satisfies the requirement. Adding the filter +ensures the CI evidence comes specifically from the required environment. + ## Common Tasks for AI Agents ### Adding a New Feature diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20eb7c7..b5ed737 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ great tool. ## Code of Conduct This project and everyone participating in it is governed by our -[Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report +[Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior through GitHub issues. ## How Can I Contribute? @@ -46,9 +46,8 @@ Enhancement suggestions are tracked as GitHub issues. When creating an enhanceme ### Prerequisites -- [.NET SDK 8.0, 9.0, or 10.0](https://dotnet.microsoft.com/download) -- A code editor ([Visual Studio](https://visualstudio.microsoft.com/), - [Visual Studio Code](https://code.visualstudio.com/), or [JetBrains Rider](https://www.jetbrains.com/rider/)) +- [.NET SDK 8.0, 9.0, or 10.0][dotnet-download] +- A code editor ([Visual Studio][vs], [Visual Studio Code][vscode], or [JetBrains Rider][rider]) ### Getting Started @@ -251,7 +250,7 @@ TestResults/ Releases are managed by the project maintainers: -1. Version numbers follow [Semantic Versioning](https://semver.org/) +1. Version numbers follow [Semantic Versioning][semver] 2. Releases are created through GitHub releases 3. NuGet packages are automatically published on release 4. Release notes are generated from commit messages @@ -266,3 +265,11 @@ Contributors will be recognized in the project README and release notes. We appr how small! Thank you for contributing to TestResults! 🎉 + + +[code-of-conduct]: CODE_OF_CONDUCT.md +[dotnet-download]: https://dotnet.microsoft.com/download +[vs]: https://visualstudio.microsoft.com/ +[vscode]: https://code.visualstudio.com/ +[rider]: https://www.jetbrains.com/rider/ +[semver]: https://semver.org/ diff --git a/README.md b/README.md index 2f3ae5d..5049254 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # TestResults Library -![GitHub forks](https://img.shields.io/github/forks/demaconsulting/TestResults?style=plastic) -![GitHub Repo stars](https://img.shields.io/github/stars/demaconsulting/TestResults?style=plastic) -![GitHub contributors](https://img.shields.io/github/contributors/demaconsulting/TestResults?style=plastic) -![GitHub](https://img.shields.io/github/license/demaconsulting/TestResults?style=plastic) -![Build](https://github.com/demaconsulting/TestResults/actions/workflows/build_on_push.yaml/badge.svg) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_TestResults&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=demaconsulting_TestResults) -[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_TestResults&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=demaconsulting_TestResults) -[![NuGet](https://img.shields.io/nuget/v/DemaConsulting.TestResults?style=plastic)](https://www.nuget.org/packages/DemaConsulting.TestResults) +[![GitHub forks][badge-forks]][link-forks] +[![GitHub stars][badge-stars]][link-stars] +[![GitHub contributors][badge-contributors]][link-contributors] +[![License][badge-license]][link-license] +[![Build][badge-build]][link-build] +[![Quality Gate][badge-quality]][link-quality] +[![Security][badge-security]][link-security] +[![NuGet][badge-nuget]][link-nuget] A lightweight C# library for programmatically creating test result files in TRX and JUnit formats. @@ -268,7 +268,7 @@ We welcome contributions! Please see our [Contributing Guide][contributing] for ## License -This project is licensed under the MIT License - see the [LICENSE][license] file for details. +This project is licensed under the MIT License - see the [LICENSE][link-license] file for details. ## Support @@ -280,7 +280,27 @@ This project is licensed under the MIT License - see the [LICENSE][license] file Developed and maintained by [DEMA Consulting](https://github.com/demaconsulting). + +[badge-forks]: https://img.shields.io/github/forks/demaconsulting/TestResults?style=plastic +[badge-stars]: https://img.shields.io/github/stars/demaconsulting/TestResults?style=plastic +[badge-contributors]: https://img.shields.io/github/contributors/demaconsulting/TestResults?style=plastic +[badge-license]: https://img.shields.io/github/license/demaconsulting/TestResults?style=plastic +[badge-build]: https://img.shields.io/github/actions/workflow/status/demaconsulting/TestResults/build_on_push.yaml?style=plastic +[badge-quality]: https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_TestResults&metric=alert_status +[badge-security]: https://sonarcloud.io/api/project_badges/measure?project=demaconsulting_TestResults&metric=security_rating +[badge-nuget]: https://img.shields.io/nuget/v/DemaConsulting.TestResults?style=plastic + + +[link-forks]: https://github.com/demaconsulting/TestResults/network/members +[link-stars]: https://github.com/demaconsulting/TestResults/stargazers +[link-contributors]: https://github.com/demaconsulting/TestResults/graphs/contributors +[link-license]: https://github.com/demaconsulting/TestResults/blob/main/LICENSE +[link-build]: https://github.com/demaconsulting/TestResults/actions/workflows/build_on_push.yaml +[link-quality]: https://sonarcloud.io/dashboard?id=demaconsulting_TestResults +[link-security]: https://sonarcloud.io/dashboard?id=demaconsulting_TestResults +[link-nuget]: https://www.nuget.org/packages/DemaConsulting.TestResults + + [architecture]: https://github.com/demaconsulting/TestResults/blob/main/ARCHITECTURE.md [contributing]: https://github.com/demaconsulting/TestResults/blob/main/CONTRIBUTING.md [code-of-conduct]: https://github.com/demaconsulting/TestResults/blob/main/CODE_OF_CONDUCT.md -[license]: https://github.com/demaconsulting/TestResults/blob/main/LICENSE diff --git a/SECURITY.md b/SECURITY.md index f1605ad..fa95951 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,7 +24,7 @@ findings. Instead, please report them using one of the following methods: 1. **GitHub Security Advisories** (Preferred) - - Go to the [Security tab](https://github.com/demaconsulting/TestResults/security) + - Go to the [Security tab][security-tab] - Click on "Report a vulnerability" - Fill out the form with details about the vulnerability @@ -96,7 +96,7 @@ We maintain transparency about known security issues: - No known vulnerabilities at this time Historical security advisories can be found in the -[GitHub Security Advisories](https://github.com/demaconsulting/TestResults/security/advisories) section. +[GitHub Security Advisories][security-advisories] section. ## Security Best Practices for Users @@ -130,3 +130,7 @@ directly. ## Attribution This security policy is based on security policy best practices and adapted for the TestResults project. + + +[security-tab]: https://github.com/demaconsulting/TestResults/security +[security-advisories]: https://github.com/demaconsulting/TestResults/security/advisories diff --git a/requirements.yaml b/requirements.yaml index 2d5b0ea..e04918b 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -3,6 +3,17 @@ # # This file defines the requirements for the TestResults library. # Requirements are verified through tests and documented in the requirements and trace matrix documents. +# +# Test links can include a source filter prefix (e.g. "windows@", "ubuntu@", "net8.0@") to restrict +# which test results count as evidence for a requirement. This is critical for platform and framework +# requirements - removing these filters invalidates the evidence-based proof. +# +# Source filter prefixes: +# windows@TestName - proves the test passed on a Windows platform +# ubuntu@TestName - proves the test passed on a Linux (Ubuntu) platform +# net8.0@TestName - proves the test passed under the .NET 8 runtime +# net9.0@TestName - proves the test passed under the .NET 9 runtime +# net10.0@TestName - proves the test passed under the .NET 10 runtime sections: - title: TestResults Library Requirements diff --git a/src/DemaConsulting.TestResults/TestResult.cs b/src/DemaConsulting.TestResults/TestResult.cs index 99ff07c..9b48932 100644 --- a/src/DemaConsulting.TestResults/TestResult.cs +++ b/src/DemaConsulting.TestResults/TestResult.cs @@ -51,7 +51,7 @@ public sealed class TestResult public string ClassName { get; set; } = string.Empty; /// - /// Gets the name of the computer that executed the test case + /// Gets or sets the name of the computer that executed the test case /// public string ComputerName { get; set; } = Environment.MachineName; diff --git a/test/DemaConsulting.TestResults.Tests/IO/JUnitSerializerTests.cs b/test/DemaConsulting.TestResults.Tests/IO/JUnitSerializerTests.cs index 79c1616..9f3f210 100644 --- a/test/DemaConsulting.TestResults.Tests/IO/JUnitSerializerTests.cs +++ b/test/DemaConsulting.TestResults.Tests/IO/JUnitSerializerTests.cs @@ -33,10 +33,16 @@ public sealed class JUnitSerializerTests /// /// Test for basic serialization with passed test /// + /// + /// Tests that JUnitSerializer correctly serializes a basic TestResults object with a single passed test. + /// Proves that the serializer produces valid JUnit XML with correct testsuites structure, + /// testsuite counters (tests=1, failures=0, errors=0, skipped=0), and testcase attributes + /// including proper time formatting and that no failure/error/skipped elements are present for passed tests. + /// [TestMethod] public void JUnitSerializer_Serialize_PassedTest_ProducesValidJUnitXml() { - // Construct a basic test results object + // Arrange - Construct a basic test results object with one passed test var results = new TestResults { Name = "BasicTests", @@ -52,18 +58,18 @@ public void JUnitSerializer_Serialize_PassedTest_ProducesValidJUnitXml() ] }; - // Serialize the test results + // Act - Serialize the test results to JUnit XML var xml = JUnitSerializer.Serialize(results); Assert.IsNotNull(xml); - // Parse and verify the XML structure + // Assert - Parse and verify the XML structure var doc = XDocument.Parse(xml); var root = doc.Root; Assert.IsNotNull(root); Assert.AreEqual("testsuites", root.Name.LocalName); Assert.AreEqual("BasicTests", root.Attribute("name")?.Value); - // Verify test suite + // Assert - Verify test suite attributes var testSuite = root.Element("testsuite"); Assert.IsNotNull(testSuite); Assert.AreEqual("MyTestClass", testSuite.Attribute("name")?.Value); @@ -73,14 +79,14 @@ public void JUnitSerializer_Serialize_PassedTest_ProducesValidJUnitXml() Assert.AreEqual("0", testSuite.Attribute("skipped")?.Value); Assert.AreEqual("1.500", testSuite.Attribute("time")?.Value); - // Verify test case + // Assert - Verify test case attributes var testCase = testSuite.Element("testcase"); Assert.IsNotNull(testCase); Assert.AreEqual("Test1", testCase.Attribute("name")?.Value); Assert.AreEqual("MyTestClass", testCase.Attribute("classname")?.Value); Assert.AreEqual("1.500", testCase.Attribute("time")?.Value); - // Verify no failure/error/skipped elements for passed test + // Assert - Verify no failure/error/skipped elements for passed test Assert.IsNull(testCase.Element("failure")); Assert.IsNull(testCase.Element("error")); Assert.IsNull(testCase.Element("skipped")); @@ -89,10 +95,15 @@ public void JUnitSerializer_Serialize_PassedTest_ProducesValidJUnitXml() /// /// Test for serialization with failed test /// + /// + /// Tests that JUnitSerializer correctly serializes a failed test with error message and stack trace. + /// Proves that the serializer includes a failure element with the correct message attribute and + /// stack trace content, and updates the failures counter correctly (failures=1). + /// [TestMethod] public void JUnitSerializer_Serialize_FailedTest_IncludesFailureElement() { - // Construct test results with a failed test + // Arrange - Construct test results with a failed test var results = new TestResults { Name = "FailureTests", @@ -110,11 +121,11 @@ public void JUnitSerializer_Serialize_FailedTest_IncludesFailureElement() ] }; - // Serialize the test results + // Act - Serialize the test results to JUnit XML var xml = JUnitSerializer.Serialize(results); Assert.IsNotNull(xml); - // Parse and verify the XML structure + // Assert - Parse and verify the XML structure var doc = XDocument.Parse(xml); var testSuite = doc.Root?.Element("testsuite"); Assert.IsNotNull(testSuite); @@ -122,12 +133,12 @@ public void JUnitSerializer_Serialize_FailedTest_IncludesFailureElement() Assert.AreEqual("1", testSuite.Attribute("failures")?.Value); Assert.AreEqual("0", testSuite.Attribute("errors")?.Value); - // Verify test case with failure + // Assert - Verify test case with failure element var testCase = testSuite.Element("testcase"); Assert.IsNotNull(testCase); Assert.AreEqual("Test2", testCase.Attribute("name")?.Value); - // Verify failure element + // Assert - Verify failure element contains error message and stack trace var failure = testCase.Element("failure"); Assert.IsNotNull(failure); Assert.AreEqual("Expected value to be 42 but was 0", failure.Attribute("message")?.Value); @@ -439,26 +450,33 @@ public void JUnitSerializer_Serialize_UsageExample_ProducesValidJUnitXml() /// /// Test for basic deserialization /// + /// + /// Tests that JUnitSerializer correctly deserializes basic JUnit XML with a single passed test. + /// Proves that the deserializer extracts test suite name, test count, test name, class name, + /// duration, and outcome correctly from valid JUnit XML format. + /// [TestMethod] public void JUnitSerializer_Deserialize_BasicJUnitXml_ReturnsTestResults() { - // Deserialize the test results object - var results = JUnitSerializer.Deserialize( - """ + // Arrange - Create basic JUnit XML content + var junitXml = """ - """); - Assert.IsNotNull(results); + """; - // Assert results information + // Act - Deserialize the test results object + var results = JUnitSerializer.Deserialize(junitXml); + + // Assert - Verify results information + Assert.IsNotNull(results); Assert.AreEqual("BasicTests", results.Name); Assert.HasCount(1, results.Results); - // Assert test result information + // Assert - Verify test result information var result = results.Results[0]; Assert.AreEqual("Test1", result.Name); Assert.AreEqual("MyTestClass", result.ClassName); @@ -655,10 +673,16 @@ public void JUnitSerializer_Deserialize_DefaultSuite_ReturnsEmptyClassName() /// /// Test for round-trip serialization and deserialization /// + /// + /// Tests that JUnitSerializer correctly performs round-trip serialization and deserialization. + /// Proves that serializing TestResults to JUnit XML and deserializing back preserves all key data + /// including test names, class names, durations, outcomes, error messages, stack traces, and system output. + /// This validates data integrity through the complete serialize/deserialize cycle. + /// [TestMethod] public void JUnitSerializer_Serialize_ThenDeserialize_PreservesTestData() { - // Create original test results + // Arrange - Create original test results with multiple test scenarios var original = new TestResults { Name = "RoundTripTests" }; original.Results.Add( new TestResult @@ -680,17 +704,15 @@ public void JUnitSerializer_Serialize_ThenDeserialize_PreservesTestData() ErrorStackTrace = "Stack trace here" }); - // Serialize to JUnit XML + // Act - Serialize to JUnit XML and then deserialize back var xml = JUnitSerializer.Serialize(original); - - // Deserialize back var deserialized = JUnitSerializer.Deserialize(xml); - // Verify results match + // Assert - Verify high-level results match Assert.AreEqual(original.Name, deserialized.Name); Assert.HasCount(original.Results.Count, deserialized.Results); - // Verify first test + // Assert - Verify first test (passed with system output) preserved correctly var origTest1 = original.Results[0]; var deserializedTest1 = deserialized.Results[0]; Assert.AreEqual(origTest1.Name, deserializedTest1.Name); @@ -699,7 +721,7 @@ public void JUnitSerializer_Serialize_ThenDeserialize_PreservesTestData() Assert.AreEqual(origTest1.Outcome, deserializedTest1.Outcome); Assert.AreEqual(origTest1.SystemOutput, deserializedTest1.SystemOutput); - // Verify second test + // Assert - Verify second test (failed with error details) preserved correctly var origTest2 = original.Results[1]; var deserializedTest2 = deserialized.Results[1]; Assert.AreEqual(origTest2.Name, deserializedTest2.Name); diff --git a/test/DemaConsulting.TestResults.Tests/IO/TrxExampleTests.cs b/test/DemaConsulting.TestResults.Tests/IO/TrxExampleTests.cs index 58537e5..2d63d17 100644 --- a/test/DemaConsulting.TestResults.Tests/IO/TrxExampleTests.cs +++ b/test/DemaConsulting.TestResults.Tests/IO/TrxExampleTests.cs @@ -33,30 +33,38 @@ public class TrxExampleTests /// /// Test deserializing the first example file /// + /// + /// Tests that TrxSerializer can correctly deserialize a real-world TRX file (example1.trx) + /// with multiple test outcomes including passed, failed, inconclusive, timeout, pending, and not executed. + /// Proves that the deserializer correctly parses test names, class names, outcomes, durations, + /// error messages, stack traces, and system output. + /// [TestMethod] public void TrxExampleTests_Deserialize_Example1Trx_ReturnsAllTestResults() { - // Deserialize the first example file. - var results = TrxSerializer.Deserialize( - TestHelpers.GetEmbeddedResource( - "DemaConsulting.TestResults.Tests.IO.Examples.example1.trx")); + // Arrange - Load the first example TRX file from embedded resources + var trxContent = TestHelpers.GetEmbeddedResource( + "DemaConsulting.TestResults.Tests.IO.Examples.example1.trx"); - // Confirm we got 6 test results + // Act - Deserialize the TRX content + var results = TrxSerializer.Deserialize(trxContent); + + // Assert - Verify we got all 6 test results Assert.HasCount(6, results.Results); - // Confirm the first test result + // Assert - Verify first test result (Passed) Assert.AreEqual("test1", results.Results[0].Name); Assert.AreEqual("test1", results.Results[0].ClassName); Assert.AreEqual(TestOutcome.Passed, results.Results[0].Outcome); Assert.AreEqual(44.7811567, results.Results[0].Duration.TotalSeconds, 0.001); - // Confirm the second test result + // Assert - Verify second test result (Inconclusive) Assert.AreEqual("test2", results.Results[1].Name); Assert.AreEqual("test2", results.Results[1].ClassName); Assert.AreEqual(TestOutcome.Inconclusive, results.Results[1].Outcome); Assert.AreEqual(44.7811567, results.Results[1].Duration.TotalSeconds, 0.001); - // Confirm the third test result + // Assert - Verify third test result (Failed with error details) Assert.AreEqual("test3", results.Results[2].Name); Assert.AreEqual("test3", results.Results[2].ClassName); Assert.AreEqual(TestOutcome.Failed, results.Results[2].Outcome); @@ -65,7 +73,7 @@ public void TrxExampleTests_Deserialize_Example1Trx_ReturnsAllTestResults() Assert.Contains("This unit test failed for a bad reason", results.Results[2].ErrorMessage); Assert.Contains(@"at test3() in c:\tests\test3.js:line 1", results.Results[2].ErrorStackTrace); - // Confirm the fourth test result + // Assert - Verify fourth test result (Timeout with error details) Assert.AreEqual("test4", results.Results[3].Name); Assert.AreEqual("test4", results.Results[3].ClassName); Assert.AreEqual(TestOutcome.Timeout, results.Results[3].Outcome); @@ -74,13 +82,13 @@ public void TrxExampleTests_Deserialize_Example1Trx_ReturnsAllTestResults() Assert.Contains("This unit test failed because it timed out", results.Results[3].ErrorMessage); Assert.Contains(@"at test4() in c:\tests\test4.js:line 1", results.Results[3].ErrorStackTrace); - // Confirm the fifth test result + // Assert - Verify fifth test result (Pending) Assert.AreEqual("test5", results.Results[4].Name); Assert.AreEqual("test5", results.Results[4].ClassName); Assert.AreEqual(TestOutcome.Pending, results.Results[4].Outcome); Assert.AreEqual(0.0, results.Results[4].Duration.TotalSeconds, 0.001); - // Confirm the sixth test result + // Assert - Verify sixth test result (NotExecuted) Assert.AreEqual("test6", results.Results[5].Name); Assert.AreEqual("test6", results.Results[5].ClassName); Assert.AreEqual(TestOutcome.NotExecuted, results.Results[5].Outcome); @@ -90,36 +98,44 @@ public void TrxExampleTests_Deserialize_Example1Trx_ReturnsAllTestResults() /// /// Test deserializing the second example file /// + /// + /// Tests that TrxSerializer can correctly deserialize a real-world TRX file (example2.trx) + /// with multiple passed test results from the same test class. + /// Proves that the deserializer correctly handles multiple tests with the same name and + /// accurately parses test names, class names, outcomes, and precise durations. + /// [TestMethod] public void TrxExampleTests_Deserialize_Example2Trx_ReturnsAllTestResults() { - // Deserialize the second example file. - var results = TrxSerializer.Deserialize( - TestHelpers.GetEmbeddedResource( - "DemaConsulting.TestResults.Tests.IO.Examples.example2.trx")); + // Arrange - Load the second example TRX file from embedded resources + var trxContent = TestHelpers.GetEmbeddedResource( + "DemaConsulting.TestResults.Tests.IO.Examples.example2.trx"); + + // Act - Deserialize the TRX content + var results = TrxSerializer.Deserialize(trxContent); - // Confirm we got 4 test results + // Assert - Verify we got all 4 test results Assert.HasCount(4, results.Results); - // Confirm the first test result + // Assert - Verify first test result Assert.AreEqual("AddShouldReturnCorrectValue", results.Results[0].Name); Assert.AreEqual("Gidget.Tests.MathTests", results.Results[0].ClassName); Assert.AreEqual(TestOutcome.Passed, results.Results[0].Outcome); Assert.AreEqual(0.0042209, results.Results[0].Duration.TotalSeconds, 0.001); - // Confirm the second test result + // Assert - Verify second test result Assert.AreEqual("OnePlusOneShouldNotEqualFive", results.Results[1].Name); Assert.AreEqual("Gidget.Tests.MathTests", results.Results[1].ClassName); Assert.AreEqual(TestOutcome.Passed, results.Results[1].Outcome); Assert.AreEqual(0.0008597, results.Results[1].Duration.TotalSeconds, 0.001); - // Confirm the third test result + // Assert - Verify third test result Assert.AreEqual("AddShouldReturnCorrectValue", results.Results[2].Name); Assert.AreEqual("Gidget.Tests.MathTests", results.Results[2].ClassName); Assert.AreEqual(TestOutcome.Passed, results.Results[2].Outcome); Assert.AreEqual(0.0000173, results.Results[2].Duration.TotalSeconds, 0.001); - // Confirm the fourth test result + // Assert - Verify fourth test result Assert.AreEqual("AddShouldReturnCorrectValue", results.Results[3].Name); Assert.AreEqual("Gidget.Tests.MathTests", results.Results[3].ClassName); Assert.AreEqual(TestOutcome.Passed, results.Results[3].Outcome);