Dennis Burton's Develop Using .NET

Change is optional. Survival is not required.
Tags: patterns | programming | testing

The Scenario

One of the characteristics of a good unit test is that the object under test is the only object being exercised. The problem in this scenario is that the object under test requires a dependent object, even though the functionality of the dependent object is not used in the test. To make a good test, the dependent object needs to be isolated from the class under test.

An example of this scenario might look like this:

    8     public interface IAmComplicated

    9     { void DoStuff(); }

   10 

   11     public class ClassUnderTest

   12     {

   13         private IAmComplicated complicated;

   14         public double circumference;

   15         public double radius;

   16         public ClassUnderTest(IAmComplicated externalComplicated)

   17         {

   18             if( externalComplicated == null )

   19                 throw new ArgumentNullException("complicated is required");

   20             complicated = externalComplicated;

   21         }

   22         public double DoInternalStuff()

   23         {

   24             return circumference/(2*radius);

   25         }

   26     }

The dependency in ClassUnderTest is that its constructor requires an instance of an object that implements IAmComplicated. The rather simple objective of this test is to validate that DoInternalStuff returns Pi within a reasonable amount of rounding error.

The Vocabulary

The name of this pattern is the Dummy. It is unclear to me whether this is a reference to the object that enables isolation for the test or a reference to the original author of the code. It seems to be a code smell for this scenario to even occur. However, sometimes you need to use an external library and do not have liberty of changing the code.

The solution without tools

Using only Visual Studio, add a class that implements IAmComplicated, right-click on it, and choose implement interface. Every method in the class will throw a NotImplementedException. This class meets the needs of the test because none of the methods are ever called; its only purpose is existence.This is your Dummy.

    1     public class ComplicatedDummy : IAmComplicated

    2     {

    3         public void DoStuff()

    4         {

    5             throw new NotImplementedException();

    6         }

    7     }

This Dummy class allows us to create the following test:

   39     [Test]

   40     public void DoStuffValidates()

   41     {

   42         ClassUnderTest cut = new ClassUnderTest(new ComplicatedDummy());

   43         cut.circumference = 314;

   44         cut.radius = 50;

   45         Assert.AreEqual(Math.PI, cut.DoInternalStuff(), 0.01d);

   46     }

The solution with Rhino Mocks

Rhino Mocks has the capability to create an object at runtime by reflecting the IAmComplicated interface. This gives us the capability we need without having to maintain another class in the test project. Since Rhino is reflecting the interface at runtime, adding a method to the interface at a later date does not require changes to the test code. There are several different ways Rhino could give us a placeholder for IAmComplicated. Here we will use a simple one line call to GenerateStub.

   55     MockRepository.GenerateStub<IAmComplicated>();

This one line saves us an entire file of maintenance, and our test only requires minor modification to use this technique. Place the call to Rhino as the parameter to the ClassUnderTest constructor instead of creating a new ComplicatedDummy.

   52         [Test]

   53         public void DoStuffValidates()       {

   55             ClassUnderTest cut = new ClassUnderTest(MockRepository.GenerateStub<IAmComplicated>());

   56             cut.circumference = 314;

   57             cut.radius = 50;

   58             Assert.AreEqual(Math.PI, cut.DoInternalStuff(), 0.01d);

   59         }      

Isolation of Failure

One of the primary goals of good unit tests is to identify exactly which unit caused the error. By applying this pattern, tests that cover ClassUnderTest no longer require an implementation of IAmComplicated to instantiate without failure. This helps shorten your bug hunting cycle by reducing the areas that indicate error to where the real error occurred. As you attain higher levels of isolation and better definition of units, you will find you spend much less time in the debugger. This means spending less time finding problems more time fixing them.

The Series

PatternsInTesting[2] - Stub Pattern PatternsInTesting[4] - Mock Pattern
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, strike) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview

Dennis Burton

View Dennis Burton's profile on LinkedIn
Follow me on twitter
Rate my presentations
Google Code repository

Community Events

Windows Azure Boot Camp Lansing GiveCamp