15

For unclear reasons my Nunit test fixture cannot be executed in a single run, so I'm forced to execute a few tests in separate runs. However this means that the test results are splitted over multiple output files.

Is there a tool available which can merge NUnit result XML files into a single XML file?

I've tried using the existing Nunit-summary tool, but this simply sequentially parses the XML files with the given XSL file and concatenates the result as one big file.

Instead I would like it to merge/group the results for the test cases into the right namespaces/testfixtures first and then feed it to the XSLT processor. This way all test results should be displayed by fixture even though they're not gathered in a single run.

Rob van Groenewoud
  • 1,824
  • 1
  • 13
  • 17
  • It sounds like some of your tests are modifying state that is directly affecting other tests. – Steve Guidi Sep 29 '09 at 14:31
  • You're right about that. The (complex) software under test has some known quirks, but my schedule doesn't leave time to solve this properly (yet, hopefully in later increments). This workaround at least allows me to script the complete process of executing the test cases and gathering results. For now it's just the reporting part which is bugging me. – Rob van Groenewoud Sep 29 '09 at 15:18
  • I've written a little tool myself to do the basic XML merge action. It needs some finishing though regarding the meta data and timing attributes. Once I'm happy enough with the end result, I'll try to publish it somewhere, so others might benefit from it. Stay tuned :-) – Rob van Groenewoud Oct 20 '09 at 08:18

3 Answers3

5

This is probably too late to help you, but we recently encountered a similar issue and wrote a small open source tool to help out: https://github.com/15below/NUnitMerger

From the readme:

Using in MSBuild

Load the task:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
             ToolsVersion="4.0"
             DefaultTargets="Build">
  <UsingTask AssemblyFile="$(MSBuildProjectDirectory)\..\Tools\MSBuild\15below.NUnitMerger.dll" TaskName="FifteenBelow.NUnitMerger.MSBuild.NUnitMergeTask" />
  ...

Feed it an array of files with in a target:

  <Target Name="UnitTest" DependsOnTargets="OtherThings">
    ... Generate the individual files here in $(TestResultsDir) ...

    <ItemGroup>
      <ResultsFiles Include="$(TestResultsDir)\*.xml" />
    </ItemGroup> 

    <NUnitMergeTask FilesToBeMerged="@(ResultsFiles)" OutputPath="$(MSBuildProjectDirectory)\TestResult.xml" />
  </Target>

Find the resulting combined results at OutputPath.

Using in F#

Create an F# console app and add 15below.NUnitMerger.dll, System.Xml and System.Xml.Linq as references.

open FifteenBelow.NUnitMerger.Core
open System.IO
open System.Xml.Linq

// All my files are in one directory
WriteMergedNunitResults (@"..\testdir", "*.xml", "myMergedResults.xml")

// I want files from all over the place
let myFiles = ... some filenames as a Seq

myFiles
|> Seq.map (fun fileName -> XDocument.Parse(File.ReadAllText(fileName)))
|> FoldDocs
|> CreateMerged
|> fun x -> File.WriteAllText("myOtherMergedResults.xml", x.ToString())
mavnn
  • 9,101
  • 4
  • 34
  • 52
  • This became relevant again in my current project, great timing :-) Thanks a lot for providing your solution as open source. It will definitely serve as a great example to get to a suitable solution in our current build system. – Rob van Groenewoud Jun 26 '12 at 07:52
  • Yeah, although I realized as a result of your comments that it doesn't calculate the overall times correctly. Feel free to send patches, and remember it can be referenced from C# as well; I just haven't got around to adding the example yet. – mavnn Jun 26 '12 at 08:26
4

I was using the 15below NUnitMerger above for a while, but wanted to extend it and since my F# skills are not good enough to do it there, I inspected their mechanism and implemented the following class in C# to achieve the same thing. Here is my starting code which might help anyone who also want to do this kind of manipulation in C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace RunNUnitTests
{
    public static class NUnitMerger
    {
        public static bool MergeFiles(IEnumerable<string> files, string output)
        {
            XElement environment = null;
            XElement culture = null;
            var suites = new List<XElement>();

            bool finalSuccess = true;
            string finalResult = "";
            double totalTime = 0;
            int total = 0, errors = 0, failures = 0, notrun = 0, inconclusive = 0, ignored = 0, skipped = 0, invalid = 0;
            foreach (var file in files)
            {
                var doc = XDocument.Load(file);
                var tr = doc.Element("test-results");

                if (environment == null)
                    environment = tr.Element("environment");
                if (culture == null)
                    culture = tr.Element("culture-info");

                total += Convert.ToInt32(tr.Attribute("total").Value);
                errors += Convert.ToInt32(tr.Attribute("errors").Value);
                failures += Convert.ToInt32(tr.Attribute("failures").Value);
                notrun += Convert.ToInt32(tr.Attribute("not-run").Value);
                inconclusive += Convert.ToInt32(tr.Attribute("inconclusive").Value);
                ignored += Convert.ToInt32(tr.Attribute("ignored").Value);
                skipped += Convert.ToInt32(tr.Attribute("skipped").Value);
                invalid += Convert.ToInt32(tr.Attribute("invalid").Value);

                var ts = tr.Element("test-suite");
                string result = ts.Attribute("result").Value;

                if (!Convert.ToBoolean(ts.Attribute("success").Value))
                    finalSuccess = false;

                totalTime += Convert.ToDouble(ts.Attribute("time").Value);

                if (finalResult != "Failure" && (String.IsNullOrEmpty(finalResult) || result == "Failure" || finalResult == "Success"))
                    finalResult = result;

                suites.Add(ts);
            }

            if (String.IsNullOrEmpty(finalResult))
            {
                finalSuccess = false;
                finalResult = "Inconclusive";
            }

            var project = XElement.Parse(String.Format("<test-suite type=\"Test Project\" name=\"\" executed=\"True\" result=\"{0}\" success=\"{1}\" time=\"{2}\" asserts=\"0\" />", finalResult, finalSuccess ? "True" : "False", totalTime));
            var results = XElement.Parse("<results/>");
            results.Add(suites.ToArray());
            project.Add(results);

            var now = DateTime.Now;
            var trfinal = XElement.Parse(String.Format("<test-results name=\"Merged results\" total=\"{0}\" errors=\"{1}\" failures=\"{2}\" not-run=\"{3}\" inconclusive=\"{4}\" ignored=\"{5}\" skipped=\"{6}\" invalid=\"{7}\" date=\"{8}\" time=\"{9}\" />", total, errors, failures, notrun, inconclusive, ignored, skipped, invalid, now.ToString("yyyy-MM-dd"), now.ToString("HH:mm:ss")));
            trfinal.Add(new[] { environment, culture, project });
            trfinal.Save(output);

            return finalSuccess;
        }

    }
}
1

I read on the web that Nunit result files are XML so i guess you can merge the file with an ordinary merge software as WinMerge

Ephemere
  • 121
  • 1
  • 6
  • You are right about the result files being XML, and indeed merging should be possible with off the shelf tooling. However, the result file also includes metrics like total duration and duration per test case. Of course the totals would not match on a merged file without recalculating them. – Rob van Groenewoud Jul 27 '10 at 06:45