Unit Testing in .NET Projects
Pages: 1, 2, 3, 4, 5
MbUnit
This is where we see some of the most innovative work in unit testing, regardless of language. There are 11 types of test fixtures, nine types of tests, and a myriad of other attributes to be applied. What one can do with MbUnit boggles the mind. So let's start with something reasonable; let's build on an existing example.
[RowTest()]
[Row(1,2,3)]
[Row(2,3,5)]
[Row(3,4,8)]
[Row(4,5,9)]
public void tAdd(Int32 x, Int32 y, Int32 expectedSum)
{
Int32 Sum;
Sum = this.Subject.Add(x,y);
Assert.AreEqual(expectedSum, Sum);
}
The attribute Test has been replaced with RowTest. The attribute Row
decorates providing data to be passed as parameters to the test. Notice the
signature to the test: (Int32 x, Int32 y, Int32 expectedSum). MbUnit's test
runner will run the test for as many Row decorators have been applied; four, in
this case. And yes, the test runner will
count them as separate tests. The results of running these tests would be four
tests run, three passed, one failed. The RowTest leads into the XMLDataProvider
attribute. It is similar to the functionality of a FitNesse fixture. The general
idea is to provide an XML file and XPath query. The test runner will loop
through the nodes, passing the node or (if desired) deserializing the node and
passing it as a parameter to the test. Again, the test runner will count each
run of the test as a test (i.e., three nodes would be counted as three tests). Jumping
up in complexity, MbUnit provides advanced fixtures like the attribute TestSuite, which can be combined with other features like reflection to produce a test like this:
[TestSuite]
public TestSuite TestGenerator(){
this.LoadAssemblies();
TestSuite suite = new TestSuite("ExceptionSuite");
foreach (Assembly Asm in AppDomain.CurrentDomain.GetAssemblies())
{
if (!(Asm.FullName.StartsWith("MbUnit")))
{
foreach (Type Tp in Asm.GetExportedTypes())
{
if (Tp.IsSubclassOf(typeof(Exception)))
{
suite.Add(Tp.FullName, new TestDelegate(GenericTest), Tp);
}
}
}
}
return suite;
}
The method decorated with the attribute TestSuite must return a TestSuite. To
add to the TestSuite, a name for the test and an instance of the delegate
TestDelegate must be provided. In the example, we will be testing the
serialization and deserialization of exceptions. You will be surprised at how
many types of exceptions are not able to be serialized or deserialized. The
method LoadAssemblies reads a config file and loads all assemblies specified. A
test is added to the TestSuite for each type found that is derived from
Exception.
public void GenericTest(Type exceptionType){
if (exceptionType == typeof(System.Data.SqlTypes.SqlTypeException))
{
Assert.Ignore("The type {0} does not implement ISerializable correctly.",
exceptionType.FullName);
}
else if (exceptionType == typeof(System.Data.SqlTypes.SqlNullValueException))
{
Assert.Ignore("The type {0} does not implement ISerializable correctly.",
exceptionType.FullName);
}
else if (exceptionType == typeof(System.Data.SqlTypes.SqlTruncateException))
{
Assert.Ignore("The type {0} does not implement ISerializable correctly.",
exceptionType.FullName);
}
The method GenericTest was provided as the delegate for each test. Notice the
parameter exceptionType; it was specified when the test was added to the
TestSuite. The next interesting thing is MbUnit's ability to dynamically ignore
a unit test. Here we are ignoring the SqlTypes exceptions because they do not
implement ISerializable correctly.
Exception ThrownException = null;
Stream Stream = null;
Exception Clone = null;
try
{
throw ((Exception)(this.ClassFactory.CreateInstanceOf(exceptionType)));
}
catch (Exception ex)
{
if (ex.GetType() == exceptionType)
{
ThrownException = ex;
}
else
{
Assert.Ignore(string.Format("Not able to create {0} {1}",
exceptionType.FullName, ex.ToString()));
}
}

