This is a copy of an upcoming CodeProject article (number 36)
This article presents a new way of creating unit tests. Rather than creating a fixture for each class, we split the testing effort by class functionality. Taking advantage of interface composition, we use split the unit tests for each interface and we feed those fixtures using factories. This is why I call this technique: Composite Unit Testing.
There are several advantages of using this approach:
In the rest of the article, I will illustrate this technique on ArrayList and Hashtable.
ArrayList
Hashtable
Let us consider illustrate the process with two classes of the System.Collections namespace: ArrayList and Hashtable.
System.Collections
The two classes belong to different families of containers: ArrayList is a sequential container, while Hashtable is an associative container. However, as the interface diagram below shows, they share a lot of functionalities (enumeration, cloneable, serialization, etc...). These functionalities are usually represented by interface composition: ICloneable, ISerializable, etc... The interface define functionalities and requirements on those functionalities.
ICloneable
ISerializable
If you take the usual unit testing methodology, you will need to write two (huge) fixture to test the two classes. Since they share functionality, you will end up duplicating testing code, maintenance problem will increase, etc...
Composite unit testing provides a flexbile solution to those problems. In the following, I will illustrate how it is implemented in MbUnit.
As mentionned in the introduction, composite unit testing fits naturally in the TDD idea. The principal steps of the process are:
This is where you define the functionalities and the requirements. If the documentation is clear enough, it should translate naturally into unit tests. (In this example, the job is already done).
MbUnit defines a new custom attribute TypeFixture that is used to create fixture for types (classes, structs or interface). TypeFixture constructor take the type that is tested as argument. Let us start with the fixture of IEnumerable:
TypeFixture
IEnumerable
using System; using System.Collections; using MbUnit.Core.Framework; using MbUnit.Framework; [TypeFixture(typeof(IEnumerable))] public class EnumerableTest {}
The test case in EnumerableTest will receive an instance of the tested type (IEnumerable here) as argument. Therefore, the correct signature of those methods is as follows:
[TypeFixture(typeof(IEnumerable))] public class EnumerableTest { [Test] public void EmptyTest(IEnumerable en) {...} }
The argument is the only difference with the "classic" unit test. You can use test decorators like ExpectedException, Ignore, etc... as usual. IEnumerable defines one method, GetEnumerator. The only requirement is that the IEnumerator instance is not a null reference:
ExpectedException
Ignore
GetEnumerator
[TypeFixture(typeof(IEnumerable))] public class EnumerableTest { [Test] public void GetEnumeratorNotNull(IEnumerable en) { Assert.IsNotNull(en.GetEnumerator()); } }
That's pretty short but there is nothing else to test. If you want to test the enumeration, you need to write another fixture for IEnumerator. By defining fixtures for each interface you quickly increase the coverage of the tested code.
IEnumerator
(We have skipped step 3, the implementation step)
A factory is simply a class that defines public properties or method (with no arguments) that return an object to be tested. You can use factories to provide different flavor of the same class: an empty ArrayList, randomly filled, ordered filled, etc... For example, a possible factory for ArrayList is:
public class ArrayListFactory { public ArrayList Empty { get { return new ArrayList(); } } public ArrayList RandomFilled() { ArrayList list = new ArrayList(); Random rnd = ...; for(int i=0;i<15;++i) list.Add(rnd.Next()); return list; } }
Note that a factory does not need any particular attributes. Similarly we can define HashtableFactory.
HashtableFactory
Linking the factories to the fixtures is simply done by using another custom attribute: ProviderFactory.
ProviderFactory
[TypeFixture(typeof(IEnumerable))] [ProviderFactory(typeof(ArrayListFactory),typeof(IEnumerable))] [ProviderFactory(typeof(HashtableFactory),typeof(IEnumerable))] public class EnumerableTest {...}
ProviderFactory takes the type of the factory, and the tested type as argument. The framework will take care of exploring by reflection the factories, select the suitable properties and feed the fixtures with created test instances.
The full source of the example is available in the demo project. You need to create a new C# assembly project and add the reference to MbUnit.Core.dll and MbUnit.Framework.dll, which you can download from the MbUnit web site: http://mbunit.tigris.org. /
Launch MbUnit.GUI and load the assembly (right click -> Assemblies -> Add Assemblies...). Here are some screenshots of the application:
This article has presented composite unit testing, a new strategy for designing and implementing unit testing. Awaiting comments :)
Page rendered at Monday, September 08, 2008 6:47:08 PM UTC
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.