Click here to Skip to main content
Click here to Skip to main content

Composite Unit Testing with MbUnit

By , 16 May 2004
 

Introduction

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:

  1. Expressing requirements: interfaces are a natural place for expressing requirements, which later on translate into unit tests,
  2. Test reusability: once you have design a test suite for an interface, you can apply it to any class that implements this interface,
  3. Test Driven Developement: this approach fits nicely and naturally into the TDD paradigm since execution path is interface -> interface fixture -> implementation(s).
  4. Separation of tests and tested instance generation: the code that generates the tested instances is located in factories that can be reused for each fixtures.

In the rest of the article, I will illustrate this technique on ArrayList and Hashtable.

Test Case

Let us consider illustrate the process with two classes of the System.Collections namespace: ArrayList and Hashtable.

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.

ArrayList and Hashtable

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 flexible solution to those problems. In the following, I will illustrate how it is implemented in MbUnit.

Composite Unit Testing Methodology

As mentioned in the introduction, composite unit testing fits naturally in the TDD idea. The principal steps of the process are:

  1. create the interface and express requirements,
  2. create the interface fixture and translate the requirements into unit tests,
  3. implement the interface,
  4. create a class factory that provides instances of the interface,
  5. link fixture to factories and run...

Step 1: Create the interface

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).

Step 2: Create the interface fixture (for IEnumerable)

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:

EnumerableTest

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:

[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.

Composition of tests

Step 4: Create the factories

(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.

Step 5: Linking the fixtures to the factories

Linking the factories to the fixtures is simply done by using another custom attribute: 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.

Factories

Step 6: Running the tests

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.%20/

Launch MbUnit.GUI and load the assembly (right click -> Assemblies -> Add Assemblies...). Here are some screenshots of the application:

MbUnit GUI

MbUnit report

Conclusion

This article has presented composite unit testing, a new strategy for designing and implementing unit testing. Awaiting comments :)

Reference

  1. MbUnit.
  2. My blog :), http://blog.dotnetwiki.org/.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Jonathan de Halleux
Engineer
United States United States
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAnybody ever reads these topics?memberForis Gabor14-Dec-04 3:19 
I posted a topic more than a month ago and nobody answered yet. It addresses a seroius mistake in MBUnit and nobody justified or denied its existence in the software.
 
FG
:confused
AnswerRe: Anybody ever reads these topics?memberMarc Stober21-Dec-06 4:29 
I'm sure this is too late for the original poster but for anyone else who gets here - try posting to the MbUnit user group at http://groups.google.com/group/MbUnitUser.
Generaltypefixture parameter problemmemberforis.gabor@stud.u-szeged.hu16-Nov-04 2:40 
Hello,
 
I wonder whether you could help me.
I am trying to use the TypeFixture feature of mbunit. I have written my own classes and test and it compiled all right. When I tried to run the tests in MbUnit I got an error about the number of parameters in the [Test] method. The parameter was the interface to be tested.
After this I downloaded the sample classes from http://www.codeproject.com/csharp/JdhCompositeUnitTesting.asp but I got the same result:
 

Message: Parameter count mismatch.
 
Type: System.Reflection.TargetParameterCountException
Source: mscorlib
TargetSite: System.Object Invoke(System.Object, System.Reflection.BindingFlags, System.Reflection.Binder, System.Object[], System.Globalization.CultureInfo)
HelpLink: null
Stack:   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at MbUnit.Core.TypeHelper.Invoke(MethodInfo method, Object o, IList args)
   at MbUnit.Core.Invokers.MethodRunInvoker.InvokeMethod(Object o, IList args)
   at MbUnit.Core.Invokers.MethodRunInvoker.Execute(Object o, IList args)
   at MbUnit.Core.RunPipeStarter.Run(Object fixture)
 
I am running mbunit 2.22.0.0.
Any idea about why it does not work?
 
regards,
Gabor
GeneralI had the same problem with MbUnit 2.22membertim73di4-Apr-05 7:18 
I'm having the same problem on the MbUnit 2.22 embedded in the TestDriven.NET 1.0
 
Jonathan, or anybody who knows why, please help!
 
Thanks so much !

AnswerRe: I had the same problem with MbUnit 2.22memberLuckyJames15-Aug-06 4:04 
I came across this too.
 
It seems in later versions the [Factory] attribute is required. Have a goose at the documentation.
QuestionHow does this test all members?memberKent Boogaart30-May-04 14:27 
I'm a little unsure how this tests all an interface's members. Take ICollection, for example. Your CollectionTest implementation would receive an instance of ICollection manufactured via an appropriate factory.
 
Say you wanted to test that ICollection.Count was returning the correct value. How would you test this without knowledge of the factory method called?
 
Thanks,
Kent
AnswerRe: How does this test all members?memberJonathan de Halleux1-Jun-04 23:42 
There you have a point Smile | :)
 
You cannot test ICollection.Count using Composite Testing now. You could test it using IList test interface, but that's not the point. A way of overcoming the limitation would be the ability for the user to give a "gold" instance along with the test instance. The gold instance would be mock of the interface as it "should" be and could be used to do the Count test:
 
The gold instance class (mock):
// the mock
public class CollectionMock : ICollection
{
    private int count=0;
    ...
    public int Count
    { 
        get{ return count;}
        set{this.count=value;}
    }
    ...
}
 
The tested instance factory
public class ArrayListFactory
{
    // empty provider + mock
    [Factory]
    public ArrayList Empty
    {
        get
        {
            return new ArrayList();
        }
    }
    [MockFactory]
    public MockCollection EmptyMock // name is important must be "Method"+Mock
    {
        get
        {
            MockCollection col = new MockCollection();
            return col;
        }
    }
    // 1 provider + mock
    [Factory]
    public ArrayList One
    {
        get
        {
            return Empty.Add(null);
        }
    }
    [MockFactory]
    public MockCollection OneMock // name is important must be "Method"+Mock
    {
        get
        {
            MockCollection col = new MockCollection(); 
            col.Count=1;
            return col;
        }
    }
}
 
The test:
[...] 
public class CollectionTest
{
    [Test]
    public void CountTest(ICollection tested, ICollection mock)
    {
        Assert.AreEqual(mock.Count,tested.Count,"ICollection.Count");
    }
}
 
The framework would detect if a mock is available and automatically feed it to the method. What do you think ?
 
Jonathan de Halleux - My Blog - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

GeneralRe: How does this test all members?memberKent Boogaart2-Jun-04 14:09 
Sounds fair. It will complicate the factory where many fields in the tested object must be mocked. Each mock factory method will have to set every one of those fields.
 
Might be worth ensuring the framework warns when a "mock test" is defined but the factory does not produce any mock objects. For example, if your CollectionTest was defined as in your example and your factory did not actually produce any mock objects. In that case the framework would never call this test method which is probably not intended.
 
Incidentally, do you know of any .NET / C# mock object generators? Any code-generating frameworks such as Java's XDoclet?
 
Regards,
Kent
GeneralRe: How does this test all members?memberJonathan de Halleux2-Jun-04 21:09 
Kent Boogaart wrote:
Incidentally, do you know of any .NET / C# mock object generators? Any code-generating frameworks such as Java's XDoclet?
 
I have written a CodeSmith template that creates mock objects using Reflection
http://blog.dotnetwiki.org/archive/2004/05/12/204.aspx[^]
 
Jonathan de Halleux - My Blog - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

GeneralInteresting article.memberDavid Piepgrass27-May-04 12:29 
By the way, what tool do you use to create all those nifty diagrams in your article?
GeneralRe: Interesting article.memberJonathan de Halleux27-May-04 15:29 
Thanks. I'm using CorelDraw Smile | :)
 
Jonathan de Halleux - My Blog - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

Generalintegration sharpdevelopmemberD4Skunk17-May-04 21:55 
Hello there,
 
Again another great article !!!
I noticed in your screenshot you are using sharpdevelop. Ever thought of integrating your test framework as an add-in for sharpdevelop ? I'd sure be interested Wink | ;)
 
Tom
 
Core
GeneralRe: integration sharpdevelopmemberJonathan de Halleux17-May-04 22:33 
Yes, I'm using SharpDevelop on a machine that does not support VS.NET (not enough space). The latest version is really better but it is still desesperatly lacking the debugger.
 
SharpDevelop is already bundled with NUnit, so maybe a "hack" is possible but I have not found any doc on how to write an Add-in for #devel.
 
ps: I am actively working with Jamie Cansdale to integrate MbUnit with NUnitAddIn which makes VS integration a piece of cake. In fact, in the future, MbUnit should be distributed as a bundle MbUnit/NUnitAddIn.
 
Jonathan de Halleux - My Blog - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

GeneralRe: integration sharpdevelopmemberD4Skunk18-May-04 0:07 
Jonathan de Halleux wrote:
The latest version is really better but it is still desperatly lacking the debugger.
 
From their wiki:
Add a new Tool (Tools->Options->Tools->External Tools->Add)
Title it Debugger or something like that
In Command search for DbgCLR.exe (usually in C:\Program Files\Microsoft.NET\SDK\v1.1\GuiDebug\DbgCLR.exe)
In Arguments type "${TargetPath}" (NOTE: type the quotes too!)
Hit the OK-Button
Now you can start the debugger with a single click!
 
Jonathan de Halleux wrote:
I am actively working with Jamie Cansdale to integrate
 
Smile | :)
 
Tom
 
Core
GeneralRe: integration sharpdevelopmemberJonathan de Halleux18-May-04 0:21 
Thanks for the tip.
 
Jonathan de Halleux - My Blog - www.dotnetwiki.org -
MbUnit - QuickGraph - NCollection

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130617.1 | Last Updated 17 May 2004
Article Copyright 2004 by Jonathan de Halleux
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid