I haven’t posted for a while in my irregular series on testing and over the weekend while I was struggling with creating some tests in a personal programming project I discovered an awesome feature of MbUnit.
Early in 2010, we (DotNetNuke Corporation) decided to standardize on MbUnit as our testing framework. Many of my own projects had been written using MSTest (the framework that ships as part of Visual Studio Team System) and this weekend I decided to port one of my older projects to use this testing framework, as I am planning to do some more work on it.
In this project I had an abstract base class with two different implementations – something quite common in DotNetNuke (DNN) with our extensive use of the Provider Pattern. I had written quite a few tests in MSTest and I was frustrated that in order to write tests for both concrete implementations I had to do a lot of Copy/Paste inheritance.
In the code was a comment – “TODO: I don’t like duplicating this code – there must be a better way”. I tried using a common Test base class for each of the implementations but then all my Test logic was in a different class from the Test Fixture – which made it harder to determine what the test was actually doing.
Enter MbUnit’s ability to test Interfaces - which essentially allows you to write tests against a base type (either an interface or an abstract base class) and re-run the same tests for every implementation.
Let’s look at how this works. Rather than create an artificial example, lets look at some core .NET collection classes. The ArrayList and Hashtable classes both implement the IEnumerable interface. The IEnumerable interface has one method GetEnumerator, and we basically only need to test that this method does not return null (Listing 1)
Listing 1 – Testing ArrayList’s GetEnumerator method |
1: [TestFixture]
2: public class ArrayListTests
3: {
4: [Test]
5: public void GetEnumerator_Does_Not_Return_Null()
6: {
7: //Arrange
8: var collection = new ArrayList();
9:
10: //Act and Assert
11: Assert.IsNotNull(collection.GetEnumerator());
12: }
13: }
|
In order to write the same test for a Hashtable we would need to write a completely different test that instantiates a Hashtable instead of an ArrayList (Listing 2).
Listing 2 – Testing Hashtable’s GetEnumerator method
|
1: [TestFixture]
2: public class HashtableTests
3: {
4: [Test]
5: public void GetEnumerator_Does_Not_Return_Null()
6: {
7: //Arrange
8: var collection = new Hashtable();
9:
10: //Act and Assert
11: Assert.IsNotNull(collection.GetEnumerator());
12: }
13: }
|
And this is what triggered my original code comment. This is not very elegant as I have just copy/pasted a whole bunch of code and it is not trivial to refactor.
Also, what if I wanted to test that the method did not return null regardless of whether the collection had any members. In MSTest I would have to create a whole new method to test this other scenario – another horrible bit of copy/paste inheritance.
We have already seen in this series that MbUnit has the ability to run multiple tests with different input parameters by using the Row attribute. MbUnit also has the ability to use a Factory attribute to generate multiple instances of objects that a suite of tests can be run against. This is how we can handle multiple concrete implementations of interfaces and abstract classes (Listing 3).
Listing 3 – Using MbUnit’s Factory attribute
|
1: public class IEnumerableTests
2: {
3: public static IEnumerable GetInstances()
4: {
5: yield return new ArrayList();
6: yield return new ArrayList { 0, 1 };
7: yield return new Hashtable();
8: yield return new Hashtable { { "a", 0 }, { "b", 1 } };
9: }
10:
11:
12: [Factory("GetInstances")]
13: public IEnumerable Instance;
14:
15: [Test]
16: public void GetEnumerator_Does_Not_Return_Null()
17: {
18: Assert.IsNotNull(Instance.GetEnumerator());
19: }
20: }
|
This is much better. We still have a simple, understandable Test method “Get_Enumerator_Does_Not_Return_Null”. The Instance variable is decorated with a Factory attribute, which specifies the “GetInstances” method as the Factory Provider. The end result is that the test is run four times – one for each IEnumerable instance created by the “GetInstances” method. If we decide that we need to test another implementation then we just need to add a couple of statements to the “GetInstances” method.
This example was trivial – there was only one test – but what if you have a complex interface or abstract base class with a large number of tests. In this situation this Factory attribute reduces the amount of work dramatically. If there are any implementation specific cases that need to be tested they could still be done the traditional way, but this approach allows us to handle most (if not all) of the shared test cases.
This article is cross-posted from my personal blog