diff --git a/src/DemaConsulting.TestResults/IO/JUnitSerializer.cs b/src/DemaConsulting.TestResults/IO/JUnitSerializer.cs
index 2ec97b4..99ddfe9 100644
--- a/src/DemaConsulting.TestResults/IO/JUnitSerializer.cs
+++ b/src/DemaConsulting.TestResults/IO/JUnitSerializer.cs
@@ -29,6 +29,26 @@ namespace DemaConsulting.TestResults.IO;
///
public static class JUnitSerializer
{
+ ///
+ /// Default suite name for tests without a class name
+ ///
+ private const string DefaultSuiteName = "DefaultSuite";
+
+ ///
+ /// Format string for time values in seconds with 3 decimal places
+ ///
+ private const string TimeFormatString = "F3";
+
+ ///
+ /// Error message for invalid JUnit XML file
+ ///
+ private const string InvalidJUnitFileMessage = "Invalid JUnit XML file";
+
+ ///
+ /// Attribute name for message in XML elements
+ ///
+ private const string MessageAttributeName = "message";
+
///
/// Serializes the TestResults object to a JUnit XML file
///
@@ -48,77 +68,7 @@ public static string Serialize(TestResults results)
// Add test suites for each class
foreach (var suiteGroup in testSuites)
{
- var className = string.IsNullOrEmpty(suiteGroup.Key) ? "DefaultSuite" : suiteGroup.Key;
- var suiteTests = suiteGroup.ToList();
-
- var testSuite = new XElement("testsuite",
- new XAttribute("name", className),
- new XAttribute("tests", suiteTests.Count),
- new XAttribute("failures", suiteTests.Count(t => t.Outcome == TestOutcome.Failed)),
- new XAttribute("errors", suiteTests.Count(t => t.Outcome == TestOutcome.Error || t.Outcome == TestOutcome.Timeout || t.Outcome == TestOutcome.Aborted)),
- new XAttribute("skipped", suiteTests.Count(t => !t.Outcome.IsExecuted())),
- new XAttribute("time", suiteTests.Sum(t => t.Duration.TotalSeconds).ToString("F3", CultureInfo.InvariantCulture)));
-
- // Add test cases
- foreach (var test in suiteTests)
- {
- var testCase = new XElement("testcase",
- new XAttribute("name", test.Name),
- new XAttribute("classname", string.IsNullOrEmpty(test.ClassName) ? "DefaultSuite" : test.ClassName),
- new XAttribute("time", test.Duration.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)));
-
- // Add failure or error information
- if (test.Outcome == TestOutcome.Failed)
- {
- var failure = new XElement("failure");
- if (!string.IsNullOrEmpty(test.ErrorMessage))
- {
- failure.Add(new XAttribute("message", test.ErrorMessage));
- }
- if (!string.IsNullOrEmpty(test.ErrorStackTrace))
- {
- failure.Add(new XCData(test.ErrorStackTrace));
- }
- testCase.Add(failure);
- }
- else if (test.Outcome == TestOutcome.Error || test.Outcome == TestOutcome.Timeout || test.Outcome == TestOutcome.Aborted)
- {
- var error = new XElement("error");
- if (!string.IsNullOrEmpty(test.ErrorMessage))
- {
- error.Add(new XAttribute("message", test.ErrorMessage));
- }
- if (!string.IsNullOrEmpty(test.ErrorStackTrace))
- {
- error.Add(new XCData(test.ErrorStackTrace));
- }
- testCase.Add(error);
- }
- else if (!test.Outcome.IsExecuted())
- {
- var skipped = new XElement("skipped");
- if (!string.IsNullOrEmpty(test.ErrorMessage))
- {
- skipped.Add(new XAttribute("message", test.ErrorMessage));
- }
- testCase.Add(skipped);
- }
-
- // Add system output
- if (!string.IsNullOrEmpty(test.SystemOutput))
- {
- testCase.Add(new XElement("system-out", new XCData(test.SystemOutput)));
- }
-
- // Add system error
- if (!string.IsNullOrEmpty(test.SystemError))
- {
- testCase.Add(new XElement("system-err", new XCData(test.SystemError)));
- }
-
- testSuite.Add(testCase);
- }
-
+ var testSuite = CreateTestSuiteElement(suiteGroup);
root.Add(testSuite);
}
@@ -129,6 +79,125 @@ public static string Serialize(TestResults results)
return writer.ToString();
}
+ ///
+ /// Creates a test suite element for a group of tests
+ ///
+ /// A grouping of test results by class name
+ /// An XElement representing a testsuite with all test cases and statistics
+ private static XElement CreateTestSuiteElement(IGrouping suiteGroup)
+ {
+ var className = string.IsNullOrEmpty(suiteGroup.Key) ? DefaultSuiteName : suiteGroup.Key;
+ var suiteTests = suiteGroup.ToList();
+
+ var testSuite = new XElement("testsuite",
+ new XAttribute("name", className),
+ new XAttribute("tests", suiteTests.Count),
+ new XAttribute("failures", suiteTests.Count(t => t.Outcome == TestOutcome.Failed)),
+ new XAttribute("errors", suiteTests.Count(t => IsErrorOutcome(t.Outcome))),
+ new XAttribute("skipped", suiteTests.Count(t => !t.Outcome.IsExecuted())),
+ new XAttribute("time", suiteTests.Sum(t => t.Duration.TotalSeconds).ToString(TimeFormatString, CultureInfo.InvariantCulture)));
+
+ // Add test cases
+ foreach (var test in suiteTests)
+ {
+ var testCase = CreateTestCaseElement(test);
+ testSuite.Add(testCase);
+ }
+
+ return testSuite;
+ }
+
+ ///
+ /// Determines if an outcome represents an error condition
+ ///
+ /// The test outcome to evaluate
+ /// True if the outcome is Error, Timeout, or Aborted; otherwise false
+ private static bool IsErrorOutcome(TestOutcome outcome)
+ {
+ return outcome == TestOutcome.Error || outcome == TestOutcome.Timeout || outcome == TestOutcome.Aborted;
+ }
+
+ ///
+ /// Creates a test case element
+ ///
+ /// The test result to serialize
+ /// An XElement representing a testcase with outcome, system output, and system error
+ private static XElement CreateTestCaseElement(TestResult test)
+ {
+ var testCase = new XElement("testcase",
+ new XAttribute("name", test.Name),
+ new XAttribute("classname", string.IsNullOrEmpty(test.ClassName) ? DefaultSuiteName : test.ClassName),
+ new XAttribute("time", test.Duration.TotalSeconds.ToString(TimeFormatString, CultureInfo.InvariantCulture)));
+
+ // Add failure or error information based on outcome
+ AddOutcomeElement(testCase, test);
+
+ // Add system output
+ if (!string.IsNullOrEmpty(test.SystemOutput))
+ {
+ testCase.Add(new XElement("system-out", new XCData(test.SystemOutput)));
+ }
+
+ // Add system error
+ if (!string.IsNullOrEmpty(test.SystemError))
+ {
+ testCase.Add(new XElement("system-err", new XCData(test.SystemError)));
+ }
+
+ return testCase;
+ }
+
+ ///
+ /// Adds the appropriate outcome element (failure, error, or skipped) to a test case
+ ///
+ /// The testcase element to add the outcome element to
+ /// The test result containing the outcome information
+ private static void AddOutcomeElement(XElement testCase, TestResult test)
+ {
+ if (test.Outcome == TestOutcome.Failed)
+ {
+ var failure = CreateFailureOrErrorElement("failure", test);
+ testCase.Add(failure);
+ }
+ else if (IsErrorOutcome(test.Outcome))
+ {
+ var error = CreateFailureOrErrorElement("error", test);
+ testCase.Add(error);
+ }
+ else if (!test.Outcome.IsExecuted())
+ {
+ var skipped = new XElement("skipped");
+ if (!string.IsNullOrEmpty(test.ErrorMessage))
+ {
+ skipped.Add(new XAttribute(MessageAttributeName, test.ErrorMessage));
+ }
+ testCase.Add(skipped);
+ }
+ }
+
+ ///
+ /// Creates a failure or error element with message and stack trace
+ ///
+ /// The name of the element to create ("failure" or "error")
+ /// The test result containing error message and stack trace
+ /// An XElement with the specified name containing message attribute and stack trace content
+ private static XElement CreateFailureOrErrorElement(string elementName, TestResult test)
+ {
+ var element = new XElement(elementName);
+
+ if (!string.IsNullOrEmpty(test.ErrorMessage))
+ {
+ element.Add(new XAttribute(MessageAttributeName, test.ErrorMessage));
+ }
+
+ if (!string.IsNullOrEmpty(test.ErrorStackTrace))
+ {
+ element.Add(new XCData(test.ErrorStackTrace));
+ }
+
+ return element;
+ }
+
///
/// Deserializes a JUnit XML file to a TestResults object
///
@@ -143,8 +212,7 @@ public static TestResults Deserialize(string junitContents)
var results = new TestResults();
// Get the root element (testsuites)
- var rootElement = doc.Root ??
- throw new InvalidOperationException("Invalid JUnit XML file");
+ var rootElement = doc.Root ?? throw new InvalidOperationException(InvalidJUnitFileMessage);
// Get the test suite name (from testsuites or first testsuite)
results.Name = rootElement.Attribute("name")?.Value ?? string.Empty;
@@ -157,74 +225,117 @@ public static TestResults Deserialize(string junitContents)
// Process each test suite
foreach (var testSuiteElement in testSuiteElements)
{
- // Get test cases
- var testCaseElements = testSuiteElement.Elements("testcase");
-
- foreach (var testCaseElement in testCaseElements)
- {
- // Parse test case attributes
- var name = testCaseElement.Attribute("name")?.Value ?? string.Empty;
- var className = testCaseElement.Attribute("classname")?.Value ?? string.Empty;
- var timeStr = testCaseElement.Attribute("time")?.Value ?? "0";
- var duration = double.TryParse(timeStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var timeValue)
- ? TimeSpan.FromSeconds(timeValue)
- : TimeSpan.Zero;
-
- // Determine outcome based on child elements
- var failureElement = testCaseElement.Element("failure");
- var errorElement = testCaseElement.Element("error");
- var skippedElement = testCaseElement.Element("skipped");
-
- TestOutcome outcome;
- string errorMessage = string.Empty;
- string errorStackTrace = string.Empty;
-
- if (failureElement != null)
- {
- outcome = TestOutcome.Failed;
- errorMessage = failureElement.Attribute("message")?.Value ?? string.Empty;
- errorStackTrace = failureElement.Value;
- }
- else if (errorElement != null)
- {
- outcome = TestOutcome.Error;
- errorMessage = errorElement.Attribute("message")?.Value ?? string.Empty;
- errorStackTrace = errorElement.Value;
- }
- else if (skippedElement != null)
- {
- outcome = TestOutcome.NotExecuted;
- errorMessage = skippedElement.Attribute("message")?.Value ?? string.Empty;
- }
- else
- {
- outcome = TestOutcome.Passed;
- }
-
- // Get system output and error
- var systemOutput = testCaseElement.Element("system-out")?.Value ?? string.Empty;
- var systemError = testCaseElement.Element("system-err")?.Value ?? string.Empty;
-
- // Create test result
- var testResult = new TestResult
- {
- Name = name,
- ClassName = className == "DefaultSuite" ? string.Empty : className,
- Duration = duration,
- Outcome = outcome,
- ErrorMessage = errorMessage,
- ErrorStackTrace = errorStackTrace,
- SystemOutput = systemOutput,
- SystemError = systemError
- };
-
- results.Results.Add(testResult);
- }
+ ParseTestSuite(testSuiteElement, results);
}
return results;
}
+ ///
+ /// Parses a test suite element and adds test cases to results
+ ///
+ /// The testsuite XML element to parse
+ /// The TestResults object to populate with test case data
+ private static void ParseTestSuite(XElement testSuiteElement, TestResults results)
+ {
+ var testCaseElements = testSuiteElement.Elements("testcase");
+
+ foreach (var testCaseElement in testCaseElements)
+ {
+ var testResult = ParseTestCase(testCaseElement);
+ results.Results.Add(testResult);
+ }
+ }
+
+ ///
+ /// Parses a test case element
+ ///
+ /// The testcase XML element to parse
+ /// A TestResult object populated with data from the XML element
+ private static TestResult ParseTestCase(XElement testCaseElement)
+ {
+ // Parse basic test case attributes
+ var name = testCaseElement.Attribute("name")?.Value ?? string.Empty;
+ var className = testCaseElement.Attribute("classname")?.Value ?? string.Empty;
+ var duration = ParseDuration(testCaseElement.Attribute("time")?.Value);
+
+ // Determine outcome and error information
+ var (outcome, errorMessage, errorStackTrace) = ParseOutcome(testCaseElement);
+
+ // Get system output and error
+ var systemOutput = testCaseElement.Element("system-out")?.Value ?? string.Empty;
+ var systemError = testCaseElement.Element("system-err")?.Value ?? string.Empty;
+
+ // Create test result
+ return new TestResult
+ {
+ Name = name,
+ ClassName = className == DefaultSuiteName ? string.Empty : className,
+ Duration = duration,
+ Outcome = outcome,
+ ErrorMessage = errorMessage,
+ ErrorStackTrace = errorStackTrace,
+ SystemOutput = systemOutput,
+ SystemError = systemError
+ };
+ }
+
+ ///
+ /// Parses a duration string to a TimeSpan
+ ///
+ /// The time string to parse, representing seconds as a decimal number
+ /// A TimeSpan representing the duration, or TimeSpan.Zero if parsing fails
+ private static TimeSpan ParseDuration(string? timeStr)
+ {
+ if (string.IsNullOrEmpty(timeStr))
+ return TimeSpan.Zero;
+
+ return double.TryParse(timeStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var timeValue)
+ ? TimeSpan.FromSeconds(timeValue)
+ : TimeSpan.Zero;
+ }
+
+ ///
+ /// Parses outcome information from test case child elements
+ ///
+ /// The testcase XML element to parse for outcome information
+ /// A tuple containing the test outcome, error message, and error stack trace
+ private static (TestOutcome outcome, string errorMessage, string errorStackTrace) ParseOutcome(XElement testCaseElement)
+ {
+ var failureElement = testCaseElement.Element("failure");
+ var errorElement = testCaseElement.Element("error");
+ var skippedElement = testCaseElement.Element("skipped");
+
+ if (failureElement != null)
+ {
+ return (
+ TestOutcome.Failed,
+ failureElement.Attribute(MessageAttributeName)?.Value ?? string.Empty,
+ failureElement.Value
+ );
+ }
+
+ if (errorElement != null)
+ {
+ return (
+ TestOutcome.Error,
+ errorElement.Attribute(MessageAttributeName)?.Value ?? string.Empty,
+ errorElement.Value
+ );
+ }
+
+ if (skippedElement != null)
+ {
+ return (
+ TestOutcome.NotExecuted,
+ skippedElement.Attribute(MessageAttributeName)?.Value ?? string.Empty,
+ string.Empty
+ );
+ }
+
+ return (TestOutcome.Passed, string.Empty, string.Empty);
+ }
+
///
/// String writer that uses UTF-8 encoding
///
diff --git a/src/DemaConsulting.TestResults/IO/TrxSerializer.cs b/src/DemaConsulting.TestResults/IO/TrxSerializer.cs
index 968af4f..6c232bd 100644
--- a/src/DemaConsulting.TestResults/IO/TrxSerializer.cs
+++ b/src/DemaConsulting.TestResults/IO/TrxSerializer.cs
@@ -36,6 +36,36 @@ public static class TrxSerializer
///
private static readonly XNamespace TrxNamespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
+ ///
+ /// Standard test type GUID for unit tests
+ ///
+ private const string TestTypeGuid = "13CDC9D9-DDB5-4fa4-A97D-D965CCFC6D4B";
+
+ ///
+ /// Standard test list ID for all loaded results
+ ///
+ private const string TestListId = "19431567-8539-422a-85D7-44EE4E166BDA";
+
+ ///
+ /// Name for the standard test list
+ ///
+ private const string TestListName = "All Loaded Results";
+
+ ///
+ /// Error message for invalid TRX file
+ ///
+ private const string InvalidTrxFileMessage = "Invalid TRX file";
+
+ ///
+ /// Date/time format string for invariant culture
+ ///
+ private const string DateTimeFormatString = "o";
+
+ ///
+ /// Duration format string for TimeSpan
+ ///
+ private const string DurationFormatString = "c";
+
///
/// Serializes the TestResults object to a TRX file
///
@@ -47,129 +77,227 @@ public static string Serialize(TestResults results)
var doc = new XDocument();
// Construct the root element
- var root = new XElement(TrxNamespace + "TestRun",
+ var root = CreateRootElement(results);
+ doc.Add(root);
+
+ // Add results section
+ var resultsElement = CreateResultsElement(results.Results);
+ root.Add(resultsElement);
+
+ // Add definitions section
+ var definitionsElement = CreateDefinitionsElement(results.Results);
+ root.Add(definitionsElement);
+
+ // Add test entries section
+ var entriesElement = CreateTestEntriesElement(results.Results);
+ root.Add(entriesElement);
+
+ // Add test lists section
+ var testListsElement = CreateTestListsElement();
+ root.Add(testListsElement);
+
+ // Add summary section
+ var summaryElement = CreateSummaryElement(results.Results);
+ root.Add(summaryElement);
+
+ // Write the TRX text
+ var writer = new Utf8StringWriter();
+ doc.Save(writer);
+ return writer.ToString();
+ }
+
+ ///
+ /// Creates the root TestRun element
+ ///
+ /// The test results containing test run metadata
+ /// An XElement representing the TestRun root element
+ private static XElement CreateRootElement(TestResults results)
+ {
+ return new XElement(TrxNamespace + "TestRun",
new XAttribute("id", results.Id),
new XAttribute("name", results.Name),
new XAttribute("runUser", results.UserName));
- doc.Add(root);
+ }
- // Construct the results
+ ///
+ /// Creates the Results section with all test results
+ ///
+ /// The collection of test results to serialize
+ /// An XElement containing all UnitTestResult elements
+ private static XElement CreateResultsElement(List testResults)
+ {
var resultsElement = new XElement(TrxNamespace + "Results");
- root.Add(resultsElement);
- foreach (var c in results.Results)
+
+ foreach (var test in testResults)
{
- // Construct the result
- var resultElement = new XElement(TrxNamespace + "UnitTestResult",
- new XAttribute("executionId", c.ExecutionId),
- new XAttribute("testId", c.TestId),
- new XAttribute("testName", c.Name),
- new XAttribute("computerName", c.ComputerName),
- new XAttribute("testType", "13CDC9D9-DDB5-4fa4-A97D-D965CCFC6D4B"),
- new XAttribute("outcome", c.Outcome),
- new XAttribute("duration", c.Duration.ToString("c")),
- new XAttribute("startTime", c.StartTime.ToString("o", CultureInfo.InvariantCulture)),
- new XAttribute("endTime", (c.StartTime + c.Duration).ToString("o", CultureInfo.InvariantCulture)),
- new XAttribute("testListId", "19431567-8539-422a-85D7-44EE4E166BDA"));
+ var resultElement = CreateUnitTestResultElement(test);
resultsElement.Add(resultElement);
+ }
+
+ return resultsElement;
+ }
- // Construct the output element
- var outputElement = new XElement(TrxNamespace + "Output");
- resultElement.Add(outputElement);
-
- // Construct the stdout output
- if (!string.IsNullOrEmpty(c.SystemOutput))
- {
- outputElement.Add(
- new XElement(TrxNamespace + "StdOut",
- new XCData(c.SystemOutput)));
- }
-
- // Construct the stderr output
- if (!string.IsNullOrEmpty(c.SystemError))
- {
- outputElement.Add(
- new XElement(TrxNamespace + "StdErr",
- new XCData(c.SystemError)));
- }
-
- // Skip writing the error info element if there is no error information
- if (string.IsNullOrEmpty(c.ErrorMessage) &&
- string.IsNullOrEmpty(c.ErrorStackTrace))
- {
- continue;
- }
-
- // Construct the error info element
- var errorInfoElement = new XElement(TrxNamespace + "ErrorInfo");
+ ///
+ /// Creates a single UnitTestResult element
+ ///
+ /// The test result to serialize
+ /// An XElement representing a UnitTestResult with all test attributes and output
+ private static XElement CreateUnitTestResultElement(TestResult test)
+ {
+ var resultElement = new XElement(TrxNamespace + "UnitTestResult",
+ new XAttribute("executionId", test.ExecutionId),
+ new XAttribute("testId", test.TestId),
+ new XAttribute("testName", test.Name),
+ new XAttribute("computerName", test.ComputerName),
+ new XAttribute("testType", TestTypeGuid),
+ new XAttribute("outcome", test.Outcome),
+ new XAttribute("duration", test.Duration.ToString(DurationFormatString)),
+ new XAttribute("startTime", test.StartTime.ToString(DateTimeFormatString, CultureInfo.InvariantCulture)),
+ new XAttribute("endTime", (test.StartTime + test.Duration).ToString(DateTimeFormatString, CultureInfo.InvariantCulture)),
+ new XAttribute("testListId", TestListId));
+
+ var outputElement = CreateOutputElement(test);
+ resultElement.Add(outputElement);
+
+ return resultElement;
+ }
+
+ ///
+ /// Creates the Output element with stdout, stderr, and error information
+ ///
+ /// The test result containing output and error information
+ /// An XElement containing StdOut, StdErr, and ErrorInfo child elements as appropriate
+ private static XElement CreateOutputElement(TestResult test)
+ {
+ var outputElement = new XElement(TrxNamespace + "Output");
+
+ // Add stdout if present
+ if (!string.IsNullOrEmpty(test.SystemOutput))
+ {
+ outputElement.Add(
+ new XElement(TrxNamespace + "StdOut",
+ new XCData(test.SystemOutput)));
+ }
+
+ // Add stderr if present
+ if (!string.IsNullOrEmpty(test.SystemError))
+ {
+ outputElement.Add(
+ new XElement(TrxNamespace + "StdErr",
+ new XCData(test.SystemError)));
+ }
+
+ // Add error info if present
+ if (!string.IsNullOrEmpty(test.ErrorMessage) || !string.IsNullOrEmpty(test.ErrorStackTrace))
+ {
+ var errorInfoElement = CreateErrorInfoElement(test);
outputElement.Add(errorInfoElement);
+ }
- // Construct the error message
- if (!string.IsNullOrEmpty(c.ErrorMessage))
- {
- errorInfoElement.Add(
- new XElement(TrxNamespace + "Message",
- new XCData(c.ErrorMessage)));
- }
-
- // Construct the stack trace
- if (!string.IsNullOrEmpty(c.ErrorStackTrace))
- {
- errorInfoElement.Add(
- new XElement(TrxNamespace + "StackTrace",
- new XCData(c.ErrorStackTrace)));
- }
+ return outputElement;
+ }
+
+ ///
+ /// Creates the ErrorInfo element with message and stack trace
+ ///
+ /// The test result containing error message and stack trace
+ /// An XElement containing Message and StackTrace child elements
+ private static XElement CreateErrorInfoElement(TestResult test)
+ {
+ var errorInfoElement = new XElement(TrxNamespace + "ErrorInfo");
+
+ if (!string.IsNullOrEmpty(test.ErrorMessage))
+ {
+ errorInfoElement.Add(
+ new XElement(TrxNamespace + "Message",
+ new XCData(test.ErrorMessage)));
}
- // Construct definitions
+ if (!string.IsNullOrEmpty(test.ErrorStackTrace))
+ {
+ errorInfoElement.Add(
+ new XElement(TrxNamespace + "StackTrace",
+ new XCData(test.ErrorStackTrace)));
+ }
+
+ return errorInfoElement;
+ }
+
+ ///
+ /// Creates the TestDefinitions section with all unit test definitions
+ ///
+ /// The collection of test results to create definitions for
+ /// An XElement containing all UnitTest definition elements
+ private static XElement CreateDefinitionsElement(List testResults)
+ {
var definitionsElement = new XElement(TrxNamespace + "TestDefinitions");
- root.Add(definitionsElement);
- foreach (var c in results.Results)
+
+ foreach (var test in testResults)
{
definitionsElement.Add(
new XElement(TrxNamespace + "UnitTest",
- new XAttribute("name", c.Name),
- new XAttribute("id", c.TestId),
+ new XAttribute("name", test.Name),
+ new XAttribute("id", test.TestId),
new XElement(TrxNamespace + "Execution",
- new XAttribute("id", c.ExecutionId)),
+ new XAttribute("id", test.ExecutionId)),
new XElement(TrxNamespace + "TestMethod",
- new XAttribute("codeBase", c.CodeBase),
- new XAttribute("className", c.ClassName),
- new XAttribute("name", c.Name))));
+ new XAttribute("codeBase", test.CodeBase),
+ new XAttribute("className", test.ClassName),
+ new XAttribute("name", test.Name))));
}
- // Construct the Test Entries
+ return definitionsElement;
+ }
+
+ ///
+ /// Creates the TestEntries section with all test entry mappings
+ ///
+ /// The collection of test results to create entries for
+ /// An XElement containing all TestEntry mapping elements
+ private static XElement CreateTestEntriesElement(List testResults)
+ {
var entriesElement = new XElement(TrxNamespace + "TestEntries");
- root.Add(entriesElement);
- foreach (var c in results.Results)
+
+ foreach (var test in testResults)
+ {
entriesElement.Add(
new XElement(TrxNamespace + "TestEntry",
- new XAttribute("testId", c.TestId),
- new XAttribute("executionId", c.ExecutionId),
- new XAttribute("testListId", "19431567-8539-422a-85D7-44EE4E166BDA")));
-
- // Construct the test lists
- root.Add(
- new XElement(TrxNamespace + "TestLists",
- new XElement(TrxNamespace + "TestList",
- new XAttribute("name", "All Loaded Results"),
- new XAttribute("id", "19431567-8539-422a-85D7-44EE4E166BDA"))));
-
- // Construct the summary
- root.Add(
- new XElement(
- TrxNamespace + "ResultSummary",
- new XAttribute("outcome", "Completed"),
- new XElement(
- TrxNamespace + "Counters",
- new XAttribute("total", results.Results.Count),
- new XAttribute("executed", results.Results.Count(c => c.Outcome.IsExecuted())),
- new XAttribute("passed", results.Results.Count(c => c.Outcome.IsPassed())),
- new XAttribute("failed", results.Results.Count(c => c.Outcome.IsFailed())))));
+ new XAttribute("testId", test.TestId),
+ new XAttribute("executionId", test.ExecutionId),
+ new XAttribute("testListId", TestListId)));
+ }
- // Write the TRX text
- var writer = new Utf8StringWriter();
- doc.Save(writer);
- return writer.ToString();
+ return entriesElement;
+ }
+
+ ///
+ /// Creates the TestLists section
+ ///
+ /// An XElement containing the standard TestList with the "All Loaded Results" list
+ private static XElement CreateTestListsElement()
+ {
+ return new XElement(TrxNamespace + "TestLists",
+ new XElement(TrxNamespace + "TestList",
+ new XAttribute("name", TestListName),
+ new XAttribute("id", TestListId)));
+ }
+
+ ///
+ /// Creates the ResultSummary section with test statistics
+ ///
+ /// The collection of test results to calculate statistics from
+ /// An XElement containing the ResultSummary with Counters for total, executed, passed, and failed tests
+ private static XElement CreateSummaryElement(List testResults)
+ {
+ return new XElement(
+ TrxNamespace + "ResultSummary",
+ new XAttribute("outcome", "Completed"),
+ new XElement(
+ TrxNamespace + "Counters",
+ new XAttribute("total", testResults.Count),
+ new XAttribute("executed", testResults.Count(t => t.Outcome.IsExecuted())),
+ new XAttribute("passed", testResults.Count(t => t.Outcome.IsPassed())),
+ new XAttribute("failed", testResults.Count(t => t.Outcome.IsFailed()))));
}
///
@@ -187,73 +315,100 @@ public static TestResults Deserialize(string trxContents)
// Construct the results
var results = new TestResults();
- // Get the run element
+ // Parse the run element
+ ParseRunElement(doc, nsMgr, results);
+
+ // Parse all test results
+ ParseTestResults(doc, nsMgr, results);
+
+ // Return the results
+ return results;
+ }
+
+ ///
+ /// Parses the TestRun element and populates basic result properties
+ ///
+ /// The XML document containing the TRX file
+ /// The namespace manager with TRX namespace mappings
+ /// The TestResults object to populate with run metadata
+ private static void ParseRunElement(XDocument doc, XmlNamespaceManager nsMgr, TestResults results)
+ {
var runElement = doc.XPathSelectElement("/trx:TestRun", nsMgr) ??
- throw new InvalidOperationException("Invalid TRX file");
+ throw new InvalidOperationException(InvalidTrxFileMessage);
results.Id = Guid.Parse(runElement.Attribute("id")?.Value ?? Guid.NewGuid().ToString());
results.Name = runElement.Attribute("name")?.Value ?? string.Empty;
results.UserName = runElement.Attribute("runUser")?.Value ?? string.Empty;
+ }
- // Get the results
+ ///
+ /// Parses all test result elements and adds them to the results collection
+ ///
+ /// The XML document containing the TRX file
+ /// The namespace manager with TRX namespace mappings
+ /// The TestResults object to populate with test result data
+ private static void ParseTestResults(XDocument doc, XmlNamespaceManager nsMgr, TestResults results)
+ {
var resultElements = doc.XPathSelectElements(
"/trx:TestRun/trx:Results/trx:UnitTestResult",
nsMgr);
+
foreach (var resultElement in resultElements)
{
- // Get the test ID
- var testId = resultElement.Attribute("testId") ??
- throw new InvalidOperationException("Invalid TRX file");
-
- // Get the test method element
- var methodElement =
- doc.XPathSelectElement(
- $"/trx:TestRun/trx:TestDefinitions/trx:UnitTest[@id='{testId.Value}']/trx:TestMethod",
- nsMgr) ??
- throw new InvalidOperationException("Invalid TRX File");
-
- // Get the output element
- var outputElement = resultElement.Element(TrxNamespace + "Output");
-
- // Get the errorInfo element
- var errorInfoElement = outputElement?.Element(TrxNamespace + "ErrorInfo");
-
- // Add the test result
- results.Results.Add(
- new TestResult
- {
- TestId = Guid.Parse(testId.Value),
- ExecutionId = Guid.Parse(
- resultElement.Attribute("executionId")?.Value ?? Guid.NewGuid().ToString()),
- Name = methodElement.Attribute("name")?.Value ?? string.Empty,
- CodeBase = methodElement.Attribute("codeBase")?.Value ?? string.Empty,
- ClassName = methodElement.Attribute("className")?.Value ?? string.Empty,
- ComputerName = resultElement.Attribute("computerName")?.Value ?? string.Empty,
- Outcome = Enum.Parse(resultElement.Attribute("outcome")?.Value ?? "Failed"),
- StartTime = DateTime.Parse(
- resultElement.Attribute("startTime")?.Value ??
- DateTime.UtcNow.ToString(CultureInfo.InvariantCulture),
- CultureInfo.InvariantCulture,
- DateTimeStyles.AdjustToUniversal),
- Duration = TimeSpan.Parse(
- resultElement.Attribute("duration")?.Value ?? "0",
- CultureInfo.InvariantCulture),
- SystemOutput = outputElement
- ?.Element(TrxNamespace + "StdOut")
- ?.Value ?? string.Empty,
- SystemError = outputElement
- ?.Element(TrxNamespace + "StdErr")
- ?.Value ?? string.Empty,
- ErrorMessage = errorInfoElement
- ?.Element(TrxNamespace + "Message")
- ?.Value ?? string.Empty,
- ErrorStackTrace = errorInfoElement
- ?.Element(TrxNamespace + "StackTrace")
- ?.Value ?? string.Empty
- });
+ var testResult = ParseTestResult(doc, nsMgr, resultElement);
+ results.Results.Add(testResult);
}
+ }
- // Return the results
- return results;
+ ///
+ /// Parses a single UnitTestResult element
+ ///
+ /// The XML document containing the TRX file
+ /// The namespace manager with TRX namespace mappings
+ /// The UnitTestResult element to parse
+ /// A TestResult object populated with data from the XML element
+ private static TestResult ParseTestResult(XDocument doc, XmlNamespaceManager nsMgr, XElement resultElement)
+ {
+ var testId = resultElement.Attribute("testId") ??
+ throw new InvalidOperationException(InvalidTrxFileMessage);
+
+ var methodElement = doc.XPathSelectElement(
+ $"/trx:TestRun/trx:TestDefinitions/trx:UnitTest[@id='{testId.Value}']/trx:TestMethod",
+ nsMgr) ?? throw new InvalidOperationException(InvalidTrxFileMessage);
+
+ var outputElement = resultElement.Element(TrxNamespace + "Output");
+ var errorInfoElement = outputElement?.Element(TrxNamespace + "ErrorInfo");
+
+ return new TestResult
+ {
+ TestId = Guid.Parse(testId.Value),
+ ExecutionId = Guid.Parse(
+ resultElement.Attribute("executionId")?.Value ?? Guid.NewGuid().ToString()),
+ Name = methodElement.Attribute("name")?.Value ?? string.Empty,
+ CodeBase = methodElement.Attribute("codeBase")?.Value ?? string.Empty,
+ ClassName = methodElement.Attribute("className")?.Value ?? string.Empty,
+ ComputerName = resultElement.Attribute("computerName")?.Value ?? string.Empty,
+ Outcome = Enum.Parse(resultElement.Attribute("outcome")?.Value ?? "Failed"),
+ StartTime = DateTime.Parse(
+ resultElement.Attribute("startTime")?.Value ??
+ DateTime.UtcNow.ToString(CultureInfo.InvariantCulture),
+ CultureInfo.InvariantCulture,
+ DateTimeStyles.AdjustToUniversal),
+ Duration = TimeSpan.Parse(
+ resultElement.Attribute("duration")?.Value ?? "0",
+ CultureInfo.InvariantCulture),
+ SystemOutput = outputElement
+ ?.Element(TrxNamespace + "StdOut")
+ ?.Value ?? string.Empty,
+ SystemError = outputElement
+ ?.Element(TrxNamespace + "StdErr")
+ ?.Value ?? string.Empty,
+ ErrorMessage = errorInfoElement
+ ?.Element(TrxNamespace + "Message")
+ ?.Value ?? string.Empty,
+ ErrorStackTrace = errorInfoElement
+ ?.Element(TrxNamespace + "StackTrace")
+ ?.Value ?? string.Empty
+ };
}
///