Click here to Skip to main content
15,887,325 members
Articles / Programming Languages / C#

Check NUnit test results from a batch script

Rate me:
Please Sign up or sign in to vote.
4.14/5 (4 votes)
6 Jul 2009CPOL1 min read 38K   351   15   7
A small program which allows to check results generated by the nunit-console utility.

Problem overview

If you use NUnit, you can run tests using the nunit-console utility from your build script. But, this test runner just shows the results in a console window and saves a report in an XML file. There is no functionality which allows to check the results of testing after the nunit-console utility execution and perform some actions depending on these results. Of course, there are some powerful build tools which allow to do it, for example, NAnt or FinalBuilder. But, what should you do if you still use simple .bat files to build your product? The most significant problem: how could you stop the build process if some Unit Test failed?

Proposed solution

The CheckTestResults utility helps to solve this issue. It can analyze the TestResult.xml file produced by the nunit-console utility, show all the found failures, sound a beep for each failure, and finally, stop execution and wait for user input if there was at least one failure during the testing.

The utility is written in C#, and requires .NET 2.0 (or higher). Here is the source code:

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace CheckTestResults {
    class CheckTestResultsMain {

        class ErrorItem {
            public string Name;
            public string Message;
            public string StackTrace;
        }

        private static bool pauseOnError = true;
        private static string resultFileName = "";
        private static List<ErrorItem> errors;

        static void Main(string[] args) {

            Console.WriteLine("Check NUnit test results" + 
                    " utility version 1.0.0");
            Console.WriteLine("");

            errors = new List<ErrorItem>();


            if (args.Length > 0) {
                // Load program arguments
                for (int i = 0; i < args.Length; i++) {
                    if (args[i].StartsWith("-pause:")) {

                        string s = args[i].Substring("-pause:".Length);
                        pauseOnError = bool.Parse(s);
                    }
                    else
                        resultFileName = args[i];
                }
            }
            else {
                Console.WriteLine("Usage: CheckTestResults.exe" + 
                        " <path to TestResult.xml file>");
                return;
            }


            Console.WriteLine("Processing " + 
                    resultFileName + "...");
            Console.WriteLine("");
            try {
                //Analize test results file
                FileStream fstream = new FileStream(resultFileName, 
                           FileMode.Open, FileAccess.Read);
                try {
                    XmlDocument xmlDoc = new XmlDocument();
                    xmlDoc.Load(fstream);
                    LoadTestResults(xmlDoc.DocumentElement);
                }
                finally {
                    fstream.Close();
                }
            }
            catch (Exception ex) {
                Console.ForegroundColor = ConsoleColor.Red;

                Console.WriteLine("Error occur: ");
                Console.ForegroundColor = ConsoleColor.Magenta;
                Console.WriteLine(ex.Message);
                Console.Beep();
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine("");
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);
                return;
            }


            if (errors.Count > 0) {
                Console.ForegroundColor = ConsoleColor.Red;

                //Print errors
                Console.WriteLine("Tests failed");
                Console.WriteLine("");

                for (int I = 0; I < errors.Count; I++) {
                    Console.ForegroundColor = ConsoleColor.Magenta;
                    Console.WriteLine("  " + (I + 1).ToString() + 
                            ": " + errors[I].Name);
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.Write(errors[I].Message);
                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.Write(errors[I].StackTrace);

                    Console.Beep();
                    Console.WriteLine("");
                    Console.WriteLine("");
                }

                //Stop and wait for user input
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine("");
                Console.WriteLine("Press any key to continue...");
                Console.ReadKey(true);
            }
            else {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Testing succeeded!");
                Console.ForegroundColor = ConsoleColor.Gray;
                Console.WriteLine("");
            }
        }

        private static void LoadTestResults(XmlElement rootNode) {
            foreach (XmlNode node in rootNode.ChildNodes) {
                if (node is XmlElement && 
                         node.LocalName == "test-suite")
                    LoadTestSuite((XmlElement)node);
            }
        }

        private static void LoadTestSuite(XmlElement suiteNode) {
            foreach (XmlNode node in suiteNode.ChildNodes) {
                if (node is XmlElement && 
                       node.LocalName == "results") {
                    foreach (XmlNode subNode in node.ChildNodes) {
                        if (subNode is XmlElement ) {
                            if (subNode.LocalName == "test-suite") {
                                LoadTestSuite((XmlElement)subNode);
                            }
                            else if (subNode.LocalName == "test-case") {
                                LoadTestCase((XmlElement)subNode);
                            }
                        }
                    }
                }
            }
        }

        private static void LoadTestCase(XmlElement caseNode) {
            foreach (XmlNode node in caseNode.ChildNodes) {
                if (node is XmlElement && 
                        node.LocalName == "failure") {
                    LoadTestFailure((XmlElement)node);
                }
            }
        }

        private static void LoadTestFailure(XmlElement failureNode) {
            ErrorItem error = new ErrorItem();

            error.Name = 
              failureNode.ParentNode.Attributes["name"].Value;

            foreach (XmlNode node in failureNode.ChildNodes) {
                if (node.LocalName == "message") {
                    error.Message = node.FirstChild.Value;
                }
                else if (node.LocalName == "stack-trace") {
                    error.StackTrace = node.FirstChild.Value;
                }
            }

            errors.Add(error);
        }
    }
}

Using the code

To use CheckTestResults, just call it with one parameter: the path to the TestResult.xml file produced by nunit-console. So, your build script will look like:

nunit-console MyProject\Tests\bin\Debug\MyProjectTests.dll
CheckTestsResult.exe TestResult.xml

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Korzh.com
Ukraine Ukraine
Software developer and entrepreneur.

Main projects:
* EasyQuery - ad-hoc data filtering UI for .NET applications;
* Localizer - localization tool kit for Delphi projects;

Comments and Discussions

 
QuestionHow do I use the Category attribute with nunit-console.exe? Pin
Xarzu16-Jun-16 12:17
Xarzu16-Jun-16 12:17 
The nunit-console.exe has a command line argument where you can specify which tests to run or which tests to exclude. The problem is that the examples which follow the online explanation of this leave questions to be answered.

According to <a href="http://nunit.org/index.php?p=category&amp;r=2.2.7">NUnit - Category</a>[<a href="http://nunit.org/index.php?p=category&amp;r=2.2.7" target="_blank" title="New Window">^</a>], the Category Attribute is described as:

The Category attribute provides an alternative to suites for dealing with groups of tests. Either individual test cases or fixtures may be identified as belonging to a particular category. Both the gui and console test runners allow specifying a list of categories to be included in or excluded from the run. When categories are used, only the tests in the selected categories will be run. Those tests in categories that are not selected are not reported at all.

This feature is accessible by use of the /include and /exclude arguments to the console runner and through a separate "Categories" tab in the gui. The gui provides a visual indication of which categories are selected at any time.

Test Fixture Syntax


Language Filter
namespace NUnit.Tests
{
using System;
using NUnit.Framework;

[TestFixture]
[Category("LongRunning")]
public class LongRunningTests
{
// ...
}
}


Test Syntax


Language Filter
namespace NUnit.Tests
{
using System;
using NUnit.Framework;

[TestFixture]
public class SuccessTests
{
[Test]
[Category("Long")]
public void VeryLongTest()
{ /* ... */ }
}

But does this mean that [TestFixture] is required to be added before the class or before the [Category(...)] tags?

Also, in the first example, would using /include:LongRunning be enough for the test to be included if we have /include:LongRunning at the end of a command line argument?

Likewise, in the case of the second example, would it be enough to have /include:Long in the at the end of a command line argument?

I think that both [TextFixture] and [Test] are required tags in the code. Am I correct?
AnswerRe: How do I use the Category attribute with nunit-console.exe? Pin
PIEBALDconsult16-Jun-16 12:24
mvePIEBALDconsult16-Jun-16 12:24 
GeneralMy vote of 4 Pin
Kanasz Robert28-Sep-12 5:55
professionalKanasz Robert28-Sep-12 5:55 
QuestionHave you tried just checking the return code of nunit-console? Pin
erik.f14-Feb-10 4:42
erik.f14-Feb-10 4:42 
GeneralMain should return the status Pin
bzchai8-Jul-09 2:55
bzchai8-Jul-09 2:55 
GeneralRe: Main should return the status Pin
Sergiy Korzh9-Jul-09 14:31
professionalSergiy Korzh9-Jul-09 14:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.