Enforcing a Build and Test Policy

I have just realised that VS.NET has everything in place to support a build and test policy without the use of any external add-ins! You can define and enforce your unit tests in code on a per project basis. This technique uses the little known ‘ComRegisterFunction’ attribute and ‘Register for COM interop’. If this method returns an exception then the build fails. A new app domain is created per build with a base directory of ‘bin\Debug’.

Here is an example that uses the NUnit test runner (although any test runner would do). As well as stopping the build when any tests fail, test results will be written to an XML file. Tests will only be enforced on a ‘Debug’ build. The whole thing could have been done in a few lines, but I decided to add exception handling and a few comments. ;o)

using System;
using System.IO;
using System.Runtime.InteropServices;

// By default make types invisible to COM
[assembly: ComVisible(false)]

#region Run tests after every DEBUG build
#if DEBUG
namespace Tests
{
 using NUnit.Core;
 using NUnit.Framework;
 using System.Diagnostics;

 // The test runner must be COM visible
 [ComVisible(true), ProgId("SampleTests")]
 public class TestBeforeRegistering
 {
  // NOTE: "Register for COM interop" must be enabled
  [ComRegisterFunction]
  public static void abortBuildOnTestFailure(Type t)
  {
   try
   {
    string path = new Uri(t.Assembly.CodeBase).LocalPath;
    TestSuiteBuilder builder = new TestSuiteBuilder();
    TestSuite suite = builder.Build(path);
    TestResult result = suite.Run(new NullListener());
    writeXml(path + "_tests.xml", result);
    if(result.IsFailure) { throw new Exception("Test Failure!"); }
   }
   catch(Exception e)
   {
    Debug.WriteLine(e);
    throw;
   }
  }

  // Write test results to an XML file
  private static void writeXml(string path, TestResult result)
  {
   XmlResultVisitor visitor = new XmlResultVisitor(path, result);
   visitor.visit((TestSuiteResult)result);
   visitor.Write();
  }
 }

 // An example failing test
 [TestFixture]
 public class SampleTests
 {
  [Test]
  public void FailTest()
  {
   Assertion.Fail();
  }
 }
}
#endif
#endregion

I have used the AylarSolutions.Highlight online demo for the code highlighting. If anything is broken blame Thomas. ;)

To start testing after every build all you need to do is enable 'Register for COM interop'.  This has the 'interesting' side effect of registering the test runner class as a COM object with a ProgID of 'SampleTests'.  It would be very strait forward for unit testing frameworks to support the running of named test suites!  Unfortunately there is no tidy error message when the build/test fails. All you get is " COM Interop registration failed. Exception has been thrown by the target of an invocation". Feel free to post any refinements of the idea/code you come up with.

2 Comments

  • I've been tring to find usage descriptions for the NUnit.Core routine. TestSuiteBuilder, TestSuite, etc. Where did (do) you find them ?



    K. Carter

  • Couldn't you just use a post-build event for this?



    There are a couple of unsatisfactory features of the technique you've proposed here. First, it leaves cruft in the registry - it will register your TestBeforeRegistering class as a COM class under the ProgID SampleTests. (And it will make up a CLSID. I think if you ever change your TestBeforeRegistering class it may make up a different CLSID, causing an ever-increasing amount of registry cruft to accumulate.)



    Second, the way that errors are handled is distinctly unsatisfactory.



    What does this add that you don't get with VS.NET 2003's build events?

Comments have been disabled for this content.