Monday, November 05, 2007

MbUnit vs. NUnit, the cage match








I've happily used NUnit for 4 years now, and never questioned the design. As you may know, NUnit diverges from the xUnit model by using metadata rather than inheritance to indicate test fixtures, their setup, and their tests: "With NUnit 2.0, we introduced the use of attributes to identify both fixtures and test cases. Use of attributes in this way was a natural outcome of their presence in .NET and gave us a way of identifying tests that was completely independent of both inheritance and naming conventions" (see here). There are times, however, when this new model doesn't quite serve. I have quite a few test fixtures that, as part of their setup, need to perform a similar set of operations: say, read a file, create some sort of data structures, run an expensive Solve() method, etc. I can do this by deriving my fixture classes from an abstract base class that spells out a "template pattern," namely the methods or properties that the real fixture classes must implement, which the base class, which actually has a public method with the [TestFixtureSetUp] or [SetUp], which in turn encapsulates the pattern. In this case, the derived class would implement public string ModelPath { get { return @"C:\Documents and Settings\...\model.xml" } }, and the abstract class's fixture set up would call this.Load(this.ModelPath). You get the idea. Anyway, I'm not totally happy with this. I don't like the coupling within a class hierarchy that the template pattern entails, I end up with too many, only slightly different ad hoc abstract base classes that no one else understands, and there are still things I can't do this way.

Now, I have to admit that I haven't yet explored NUnit's extensibility. My current task is to write a rather complex XSLT involving three different Muenchian groupings, and I really wanted to be able to do it incrementally, in TDD fashion. I do sort of like stepping through the transform in Visual Studio's debugger, as that's taught me a lot about XML, but I need repeatable tests. XsltUnit hasn't been updated in four years, nor has XmlUnit. The former might be suitable for a real XMLer, but I'm not one, and I don't want to displace my difficulties onto a tool. XmlUnit doesn't handle namespaces, anyway, whereas I can't avoid them. Poking around further, I stumbled on MbUnit,which does include XML diffing. That turned out not to be what I wanted, but I did learn some interesting things. MbUnit still provides the expected attributes, including the ever-handy [ExpectedException(...)], but the test pattern is really described in terms of attributes. A test runner finds a test fixture's attributes, and has the appropriate invoker create the tests. This allows test methods to accept parameters, for example; the invoker simply uses reflection to call the method with an argument described by method's attributes. You can find more information at Peli's Farm, and at the Advanced Unit Test project, but I wouldn't say that the documentation is stellar. Anyway, in order to begin to be more of a radical, and less of an early adopter, I downloaded the source code, went back and forth between it and my requirements, trying to figure out how to implement them.

So this is what I ended up with:


using System.Xml.XPath;
using MbUnit.Framework;
using Omega.NetworkModel.Tests;

namespace Omega.NetworkModel.Tests
{
/* A fixture factory should load this document:

<?xml version="1.0" encoding="utf-8"?>
<Root>
<Element Integer="1"></Element>
</Root>

*/
[MyXmlTestFixture("document.xml")]
public class TestXmlWithoutTransformFixture
{
// A test invoker should somehow get its hands on the XPathNavigable,
// compile this expression, and give me the strongly typed result.
[MyXPathExpressionTest("/*")]
public void DoSomething(XPathNodeIterator iterator)
{
Assert.AreEqual(1, iterator.Count);
}

// count() returns a double, it turns out. The invoker is invoking
// this method via reflection, so if the XPath expression returns
// something other than a double, there'll be an error.
[MyXPathExpressionTest("count(//*)")]
public void CountElements(double count)
{
Assert.AreEqual(2, count);
}

// I was surprised that the compiler does *not* expect XML-escaped stuff.
[MyXPathExpressionTest("count(//*) > 0")]
public void TryBoolean(bool hasElements)
{
Assert.IsTrue(hasElements);
}
}

// The runner should load this document, and transform it. The
// transformed result is then the basis of the tests.
[MyXmlTestFixture("document.xml")]
/*
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<!-- The default template simply copies the text value. -->
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

<xsl:template match="/">
<xsl:apply-templates select="*|@*" />
</xsl:template>
</xsl:stylesheet>
*/
[MyXsltTransform("test.xslt")]
public class TestXmlAfterIdentityTransformFixture
{
[MyXPathExpressionTest("/*")]
public void DoSomething(XPathNodeIterator iterator)
{
Assert.AreEqual(iterator.Current.NodeType, XPathNodeType.Root);
}
}

// Transform, and then establish an evaluation context (i.e., namespace mappings!).
[MyXmlTestFixture("document.xml")]
[MyXsltTransform("test.xslt")]
[MyXsltNamespaceMappingDecorator("tnt", "")]
public class TestXmlAfterIdentityTransformAndNamespaceFixture
{
[MyXPathExpressionTest("/tnt:*")]
public void DoSomething(XPathNodeIterator iterator)
{
Assert.AreEqual(iterator.Current.NodeType, XPathNodeType.Root);
}
}
}

1 comment:

Taumuon said...

I blogged similar to this a couple of years ago, where I created data-driven unit tests based on XML files.
I wanted more visibility in the tree though (each entry in the XML file to be on the tree), and Charlie Poole led me through using extensiblity to do this.
I've -finally- got this packaged into an addin, you can check it out, and my original blog posts, at rakija

Cheers,
Gary