This article will try to give an rough overview of the MbUnit vision of tests, and consequently it's architecture. It's contains some material of a previous CodeProject article.
Why MbUnit ?
Unit testing is a great tool for ensuring an application quality and frameworks like NUnit or csUnit have made it very simple to implement. However, as the number of tests begins to grow, the need for more functionalities begin to show up. The above frameworks are based on the Simple Test Pattern which is basically the sequence of SetUp, Test, TearDown actions. Although highly generic, this solution lets a lot of work to be done by the test writer. Sadely, there is no easy way to derive and include a new "fixture" type in those frameworks.
SetUp
Test
TearDown
MbUnit is simply born from the fact that I wanted a new fixture and integrating it into existing frameworks was nearly impossible (I was also resting from a knee surgery at hospital with nothing else to do than coding).
Illustrating example
In order to make things clear, I will refer to an example while explaining how MbUnit works. Let me consider the Simple Test Pattern which is implemented by most test unit framework available. This is the classic way of writing unit test as described in the figure below. A TestFixture attribute tags the test class, one SetUp method, tests are done in the Test tagged method and clean up is performed in TearDown tagged method. This is illustrated in the left of the figure.
TestFixture
Attribute -> Run -> Invoker
The kernel of MbUnit is composed of different components that work in a serial way. The first component is the fixture attribute.
The fixture attribute is used to tag the classes that contain unit tests (TestFixtureAttribute is a fixture attribute). The new thing in MbUnit is that each fixture attribute contains the execution logic of the fixture which is returned at run-time under the form of a Run (IRun interface). In the case of the example, the TestFixtureAttribute is defined as a sequence of SetUp, Test and TearDown:
public class TestFixtureAttribute : TestFixturePatternAttribute { public override IRun GetRun() { SequenceRun runs = new SequenceRun(); // setup OptionalMethodRun setup = new OptionalMethodRun(typeof(SetUpAttribute),false); runs.Runs.Add( setup ); //tests MethodRun test =new MethodRun(typeof(TestPatternAttribute),true,true); runs.Runs.Add(test); // tear down OptionalMethodRun tearDown = new OptionalMethodRun(typeof(TearDownAttribute),false); runs.Runs.Add(tearDown); return runs; } }
where
TestFixturePatternAttribute
GetRun
IRun
SequenceRun
MethodRun
OptionalMethodRun
The IRun object will create an execution tree by exploring the tagged type. Each node of the tree contains a RunInvoker (IRunInvoker interface). The RunInvoker is in charge for calling the method, garding the execptions, loading data, etc... On our sample fixture, there are two tests that the Run will extract:
When the tree is built, we just extract all the possible path from the root node to the leaves to extract the different possible tests. Each of these path is called a Pipe (RunPipe class).
In the GUI, the RunPipe instances are attached to the TreeNode nodes so you can easily select and execute separately the tests. This ensures that the test execution are isolated.
This architecture brings a lot of flexibility (and complexity) on the kind of fixtures that can be defined. Any user can define it's own fixture and use MbUnit to execute it.
Page rendered at Thursday, September 02, 2010 2:47:15 PM (Pacific Daylight Time, UTC-07:00)
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.