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:
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) {
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 {
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;
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("");
}
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