Trust is good, control is better – Effective Unit Testing in .Net

The first time I heard about Unit testing was during my undergraduate degree in 1999 while learning about Extreme Programming. Since then, it always fell into the category of “There is never enough time to do it right, but always enough time to do it over“.

The big question is wether the effort it takes to write tests is balanced out by the benefits they provide. It has been shown in a number of cases that for medium to large projects with non-trivial codebases, Unit tests do improve both development productivity and product quality. See Steve Sanderson’s blog post for a good costs/benefit analysis.

The main benefits offered by Unit tests are that they help you to:

  • design code while you’re writing it
  • verify that your implementation actually does what you intended it to do

As a consequence Unit testss make it possible to refactor safely and add a layer of documentation to your code, showing how it is intended to be used.

Before delving deeper into the topic, let’s define what a Unit Test is. Roy Osherove provides the following definition:

A unit test is an automated piece of code that invokes the method or class being tested and then checks some assumptions about the logical behavior of that method or class.

Unit Test Frameworks

There are a number of frameworks which facilitate Unit Testing capabilities for .Net. One of the most popular ones is NUnit, an open source project ported from Java’s JUnit.

In NUnit, tests are written as functions which are decorated with attributes to mark them as tests.
A typical test suite would look like this:

using System;
using NUnit.Framework;

namespace MyProject.Tests
{
    [TestFixture]
    public class CalculatorTests
    {
        private MyProject.ICalculator myCalculator;

        [SetUp]
        public void Init()
        {
            // code that will be called before each Test case.
            myCalculator = new MyProject.Calculator();
        }

        [TearDown]
        public void Clean()
        {
            // code that will be called after each Test case
            myCalculator = null;
        }

        [Test]
        public void Add_TwoIntegers_ReturnsSum()
        {
            int num1 = 3;
            int num2 = 4;
            int sum = myCalculater.Add(3,4);
            Assert.AreEqual(7, sum);
        }
    }
}

The NUnit client can detect these functions, run the tests and show the results:

Alternatively, you can use Testdriven.net – a unit testing add-in for Visual Studio which allows running tests from within Visual Studio with a single-click:

Unit Test Guidelines
An important aspect is the naming convention for test objects. Roy Osherove suggests the following rules dependent on the object under test:

  • Project: [ProjectUnderTest].Tests
  • Class: [ClassName]Tests
  • Method: [MethodName]_[StateUnderTest]_[ExpectedBehavior]

As there are guidelines for writing good code, there are criterias for good unit tests:

  • Repeatable: You must be able to measure an expected outcome reliably for known inputs.
  • Independent of other tests: Any subset of tests should be able to run in any order w/o affecting the outcome.
  • Easy to write: If it takes a lot of work to set up the inputs to a test, you aren’t going to recoup your investment in writing that test. Difficulty in writing a test usually points to a flaw in design: time to refactor.
  • Easy to understand: Ideally, the automated tests that you write should also serve as a useful form of design and requirements documentation.
  • Fast: Slow-running tests will retard productivity.

In order to avoid interdependencies between tests, it is important that no state is shared between tests.

Design for Testability
According to Jeremy Miller, “Testability is the quality of a software design that allows for automated testing in a cost-effective manner“.

The two biggest problem with writing testable code are:

  • Side effects: a method which, in addition to producing a value, also modifies some state
  • Dependencies: objects in your system that your code interacts with. These can be other business objects or external dependencies s.a. filesystems, threads, memory, time, etc.

Side-Effect free functions
It is almost trivial to test a function when all you have to do is inspect it’s return value.
Side-effect free functions are the cornerstone of functional programming, where side effects are not allowed (unless through monads).
In classic OOP though, we are encouraged to write methods which modify the internal state of an object. Writing OO code while avoiding side effects is a very important concept in Domain Driven Design.

There are several patterns on how to avoid side effects, one of which is making objects immutable – meaning that once initialized, the object cannot be changed. This may sound strange, but consider the String class in .Net, which is immutable. Eric Lippert, a leading designer and implementor of the C# language and .NET Framework wrote, that in his opinion:

“Immutable data structures are the way of the future in C#. It is much easier to reason about a data structure if you know that it will never change.”

In fact, he wrote an entire series of posts on immutability.

Dependencies
While side effects make unit testing harder, because you have to think about changes in your object’s state after you call a method, an even uglier problem are external dependencies.

If a method accesses the database or a configuration file, then your tests are not repeatable, unless you initialize the state of the DB or the file on every test run.

A much better approach is to “fake” your dependency so it behaves in a predictable manner which is under your control. In order to fake a dependency, the test routine must be able to switch the original object with a fake implementation.

Consider the following implementations of class House:

//Implementation 1: Impossible to access the variable
class House
{
    private Bedroom bedroom;

    public House()
    {
        bedroom = new Bedroom();
    }
    //more methods...
}

//Implementation 2: Possible to access the variable on object creation (via the constructor) but difficult to provide an alternative implementation
class House
{
    private Bedroom bedroom;

    public House(Bedroom b)
    {
        bedroom = b;
    }
    //more methods...
}

//Implementation 3: Possible to exchange the variable's implementation, since it is exposed by an Interface
class House
{
    private IBedroom bedroom;

    public House(IBedroom b)
    {
        bedroom = b;
    }
    //more methods...
}

In order to test House no. 3 we could create a fake object FakeBedroom which implements the interface IBedroom and lets us test the House w/o it’s dependency on Bedroom’s implementation. Instead of passing the dependency in via the constructor, we could expose it as a public Property and set it after the object is initialized. Both methods are ways to perform Dependency Injection. Ideally all of your important dependencies should be made injectable and managed via a Dependency Injection (DI) Framework s.a. Spring.Net or Castle Windsor.

Coming back to fake objects, we usually distinguish between two types:

  • Stubs: A stub is a controllable replacement for an existing dependency (or collaborator) in the system. By using a stub, you can test your code without dealing with the dependency directly.
  • Mocks: A mock is a fake object which verifies whether the object under test interacted as expected with the fake object. A mock is much like a stub, except that a mock additionally saves the history of communication, which will later be verified.

RhinoMocks
Before we will look at an example, I want to address an important point: While it is possible to create Stubs and Mocks manually, it is very tedious to do so and is generally not worth the effort. Instead there are a number of frameworks available which let you create Mocks and Stubs on the fly. The most widely used mocking framework in .Net is RhinoMocks by Oren Eini otherwise known as Ayende Rahien (Besides being the author of RhinoMocks, Oren is an active contributor of the NHibernate and Castle projects and the author of one of the most popular .Net blogs: http://ayende.com/Blog/).

In RhinoMocks fake objects can be used the following way:

  1. The framework creates the fake object which impelements a given interface
  2. The expected behavior of the fake object is “recorded”
  3. The fake object is injected into the object-to-test
  4. The method-to-test is called
  5. During the call the recorded behvior of the fake object is “played back” according to the interaction with the object-to-test
  6. The framework verifies that the all the expectations are fulfilled

Example: The following example (borrowed from Roy Osherove) shows a LogAnalyzer which depends on a WebService for error logging and on an Email Service for Notification. The Analyze() method checks if the filename is too short and if so, logs the error by calling the WebService. If that call fails, an email-notification is sent via the EmailService:

public class LogAnalyzer
{
    public IWebService Service { get; set; }
    public IEmailService Email { get; set; }

    public void Analyze(string fileName)
    {
        if ( fileName.Length < 8 )
        {
            try
            {
                service.LogError("Filename too short:" + fileName);
            }
            catch (Exception e)
            {
                email.SendEmail("a", "subject", e.Message);
            }
        }
    }
}

A test of the Analyze method using NUnit and RhinoMocks could look something like this:

[Test]
public void Analyze_WebServiceThrows_SendsEmail()
{
    MockRepository mocks = new MockRepository();
    IWebService stubService = mocks.Stub();
    IEmailService mockEmail = mocks.StrictMock();

    using(mocks.Record())
    {
        //Set up stub behavior:
        // if LogError is called...
        stubService.LogError("whatever");
        // ...then regardless of the input...
        LastCall.Constraints(Is.Anything());
        // ...throw an ecxeption!
        LastCall.Throw(new Exception("fake exception"));

        //Set up expected mock behavior:
        mockEmail.SendEmail("a","subject","fake exception");
    }

    //initialize LogAnalyzer
    LogAnalyzer log = new LogAnalyzer();

    //inject fake WebService and EmailService objects
    log.Service = stubService;
    log.Email = mockEmail;

    //call the method we want to test
    string tooShortFileName = "abc.ext";
    log.Analyze(tooShortFileName);

    //verify that all expectations on the mock object are fulfilled
    mocks.VerifyAll();
}

A thorough coverage of RhinoMocks is beyond the scope of this article, the aim of which was to briefly present a current approach to Unit Testing in .Net.

There are many excellent .Net frameworks (e.g. the built-in testing framework of Visual Studio 2010), patterns and principles (e.g. Test Driven Design) regarding unit testing in particular and software testing in general, which can greatly contribute to the quality of your code and the efficiency of your development process.

Conclusion

  • In most cases Unit Tests improve code quality and productivity
  • Design Unit Tests to be repeatable, fast and independent from each other
  • Design code for testability by reducing side-effects and managing your dependencies via a dependency injection framework
  • Make use of testing frameworks, s.a. NUnit, Testdriven.Net and RhinoMocks to make writing unit tests feasible

Resources

Manuel Korn


1 Comment

  1. 1. Tal Weinfeld

    Comment of 6. May 2010 at 20:32

    Great post! And a very important topic:

    As a Flash RIA developer, I all too often encounter glazed looks when mentioning concepts like “MVC” and “Unit Testing”. Fully aware of the abundance of good frameworks available for implementing these design patterns, it’s not entirely clear to me why so many developers choose to stay “outside the holy land”.

    RIA is all about views. Writing good unit tests for views can really boost code consolidation, and in turn, save tremendous amount of time dealing with design deficiencies late in the development process.

    Keep up the good work!

Leave a Reply