BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Testing
.NET 1.1+

Using Stubs

The twelfth part of the Automated Unit Testing tutorial discusses the use of stubs. Stubs are used to simplify the testing of code that relies upon other classes, helping a unit of code to be isolated from its dependencies.

Unit Tests and Dependencies

A key to successful unit testing is ensuring that you are performing tests that each exercise a small unit of code. A unit is usually a single method or property, is often a class and, rarely, is a group of several classes working together. In a real-world solution, methods and classes have dependencies that are called upon by a unit of code or that provide values to the code under test.

It is essential that you isolate the code under test from its dependencies. If you do not, the dependencies must be prepared in the Arrange portion of the Arrange, Act, Assert (AAA) pattern of your tests. If the dependencies themselves have dependencies you will find that the Arrange section becomes larger and the tests quickly become unwieldy and difficult to maintain. If the dependencies include integration with databases, files or specialise hardware, the tests will require access to these items and may run much more slowly as a result.

There are several ways in which you can isolate a unit of code from its dependencies. In this article we will concentrate on the use of stubs.

What is a Stub?

A stub is a simple class that implements the same interfaces as a dependency of the unit of code being tested. Instead of containing the functionality of the real dependency, a stub will return preset results from its properties and methods. These "canned answers" are created in such a way as to allow the main code to be fully tested. They also ensure that no integration with slow external systems is required; the stubs should be very fast to return their results.

NB: A similar type of class that is used to isolate a unit of code is the dummy. A dummy is a class that implements an interface but has no functionality. It is generally used where a parameter of a call must be provided but where the parameter is not used during the test.

Stub Example

We can demonstrate the use of stubs with an example. The class diagram below shows a set of classes and interfaces that work together to test the quality of water in an aquarium. Three of the interfaces are implemented by classes that control specialist hardware. The hardware includes sensors that are placed into the water being checked. These are represented by the IAmmoniaMeter, IOxygenMeter and ITemperatureSensor interfaces. The classes that implement these interfaces are not shown as they are not relevant to the example.

The fourth interface in the diagram is IO2SafetyCalculator. This interface defines a method that reads data from the oxygen and temperature sensors and uses these readings to determine if the oxygen level is suitable for fish. The results from this method and from the PartsPerMillion property of the IAmmoniaMeter interface are then used by the WaterQualityReporter class to determine if the water is safe. It is the WaterQualityReporter class that is the unit of code being tested in our example.

Water Safety Subsystem

If we were unable to isolate the WaterQualityReporter class from its dependencies, we would need to prepare four additional classes and connect the computer to three water quality sensors. We would also need sample of water of varying qualities, oxygenation levels and temperatures in order to complete the unit testing. Clearly, this would not be viable.

To resolve the isolation problem we will create some stubs. Before we start, add the following code to a project. This defines the class and the four interfaces from the class diagram. You can see that the operation of the CheckWater method in the WaterQualityReporter is quite simple. It uses a StringBuilder to combine some template text with the results from the dependency objects. The dependencies are supplied using constructor injection.

public class WaterQualityReporter
{
    IO2SafetyCalculator _o2SafetyCalculator;
    IAmmoniaMeter _ammoniaMeter;

    public WaterQualityReporter(IO2SafetyCalculator o2, IAmmoniaMeter ammonia)
    {
        _o2SafetyCalculator = o2;
        _ammoniaMeter = ammonia;
    }

    public string CheckWater()
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("Oxygen: ");
        sb.Append(_o2SafetyCalculator.OxygenLevelIsSafe() ? "OK" : "Low");
        sb.Append(", Ammonia: ");
        sb.Append(_ammoniaMeter.PartsPerMillion == 0 ? "OK" : "High");
        return sb.ToString();
    }
}

public interface IO2SafetyCalculator
{
    bool OxygenLevelIsSafe();
}

public interface IAmmoniaMeter
{
    int PartsPerMillion { get; }
}

public interface IOxygenMeter
{
    int OxygenPercentage { get; }
}

public interface ITemperatureSensor
{
    double Celcius { get; }
}
28 April 2011