Writing Testable Code - It's all about dependencies


Following on from my post about why you should write automated tests, I wanted to talk about how to write code that is testable. Unfortunately, unless you explicitly know how to write testable code, and are familiar with the SOLID principles (which from my experience, the average developer isn't!) - then the natural way we write code, and the way we typically learn to code makes it very hard to write unit/integration tests against our code.

This post will be in the context of .NET development, and the examples I provide will be in C#. But a lot of the concepts will also apply to other languages.

As I said in the title - it's all about dependencies - or more to the point, about stopping your code from forcing/limiting itself to using a particular implementation of its dependencies.

An example might be a database or a web service call. If you can't run your code and stop it from physically hitting a database, then you can't run tests against that code without those tests also hitting the database. And if you're hitting a physical database - how do you manage the state of the database for the tests? It makes much more sense to provide test data to your code directly from the tests. And the tests will also run a lot faster if you're not hitting actual databases / webservices.

Obviously some of your integration tests might actually want to hit those physical endpoints - but you should at least structure your code so you have a choice.

So, let's show a couple of examples of code that isn't testable ...

public class MyClass
{
    public void MyMethod()
    {
        var database = new SqlServerDatabaseContext();

        var data = database.GetSomeDataFromDatabase();

        // do something with data
    }
}

In this example, we're creating an instance of the database context dependancy inside our code. So if we wanted to write a unit test against MyMethod(), then that test can't run MyMethod without it calling SqlServerDatabaseContext.GetSomeDataFromDatabase, which hits a physical database. Every time you see yourself writing the keyword new, you should always think about the implications of coupling your code to that dependency.

Likewise, if the SqlServerDatabaseContext class used static methods instead, then the same problem applies ...

public class MyClass
{
    public void MyMethod()
    {
        var data = SqlServerDatabaseContext.GetSomeDataFromDatabase();

        // do something with data
    }
}

So what can we do about this? The solution is to instantiate the dependancies outside of the code you want to test, then pass that instance into your code. This is called dependency injection. The most common way of doing this is via the constructor. However, you can also pass the instance to your code by setting a property, or passing it as a parameter to a specific method in your class.

Here's a version of the code above, except it injects the instance as a parameter to the MyMethod method...

public class MyClass
{
    public void MyMethod(SqlServerDatabaseContext databaseContext)
    {
        var data = databaseContext.GetSomeDataFromDatabase();

        // do something with data
    }
}

So we're now no longer creating the instance inside our code, but we still have a problem. We still can't stop the code from calling SqlServerDatabaseContext.GetSomeDataFromDatabase(). This is where interfaces or abstract classes come into play. By abstracting out and separating the method signatures from the actual implementation, you can swap the implementation at will...

public class MyClass
{
    public void MyMethod(IDataContext dataContext)
    {
        var data = dataContext.GetSomeDataFromDatabase();

        // do something with data
    }
}

This is better. The production code can continue to pass in an instance of SqlServerDatabaseContext so our code talks to the physical database (providing SqlServerDatabaseContext implements IDataContext that is). But our tests can pass in a different implementation of IDataContext which can just return hardcoded test data when GetSomeDataFromDatabase is called. This way you can have multiple tests passing in different data to test again.

If you wanted to use constructor injection, then a common pattern is to store the dependency as a private field ...

public class MyClass
{
    private IDataContext _dataContext;

    public MyClass(IDataContext dataContext)
    {
        _dataContext = dataContext;
    }

    public void MyMethod()
    {
        var data = _dataContext.GetSomeDataFromDatabase();

        // do something with data
    }
}

Whilst this adds a bit more code - it not only means you don't have to worry about passing your dependencies to each of the methods in your class, but it also makes much more sense when using a dependency injection framework (known as an IoC Container). I'll discuss IoC containers in my next post, as it's important to understand that you don't have to use an IoC container to do dependency injection and to write testable code. But the basic idea is that you map/bind your dependencies at your application startup (eg. bind IDataContext to SqlServerDatabaseContext, etc). Then the IoC Container will manage injecting the dependencies automagically throughout your code.

In the next few posts, I'll talk about both IoC Containers, and also using Mocking libraries to aid in writing test implementations of your interfaces in your test code.

Search


Recent Posts


Featured Posts


.NET Oxford Links