A testability case: How we went from Greenfield to legacy and back Part 2


Yesterday I blogged about some challenges we where having with a project. Today I want to have a look at how we fixed it and what we learned from it.

First, we sat down in a brainstorm session to get clear what we wanted to change.
This resulted in the following items:

  1. Improve testability by introducing Dependency Injection.
  2. Upgrade the Entity Framework to the latest version and dump Self Tracking Entities.
  3. Remove WCF.
  4. Create a basic test harness for the common use cases.

Dependency Injection

I started out with the first one, introducing Dependency Injection. Dependency Injection comes down to ‘asking for what you need instead of constructing it yourself’.

So instead of the following code:

public class Untestable
    public Person GetById(Guid id)
        using (IUnitOfWork uow = UnitOfWorkFactory.Create())
            return uow.PersonRepository.GetById(id);

You change it to this:

public class Testable
    private IPersonRepository personRepository;

    public Testable(IPersonRepository personRepository)
        this.personRepository = personRepository;

    public Person GetById(Guid id)
        return personRepository.GetById(id);

The big advantage is that you can now insert dependencies in your class trough the constructor. You depend on an interface instead of an implementation. In your Unit Tests you can use a mocking framework like Rhino Mocks, pass a mock or stub to the constructor and test your code. This will result in really fast unit tests (because you don’t hit a database anymore) and you can test each aspect of your code.

I choose to use Ninject as our Dependency Injector. It has a nice, fluent interface that is easy to learn. We split all your dependencies in modules (one for each layer) and made all classes internal so we can’t instantiate them directly.

Then I started looking trough our code for all ‘new’ operators or factory calls. After moving these to the constructor I got a lot of compile errors from our ASPX pages. All those direct constructor calls where replaced with Ninject and suddenly, our project was up and running again!

This immediately resulted in better testability. We can now test a class in complete isolation without having any side effects!

Entity Framework & WCF

To upgrade the Entity Framework we switched to using the Nuget package. I removed all existing references, installed the Nuget package and tweaked the T4 template. This gives us the advantage of having compiled queries everywhere and the easier to use DbContext.

After these changes, removing WCF was only a matter of deleting the project. Ninject was configured to construct the in-memory version of the services and WCF wasn’t referenced anywhere directly.

Creating a basic test harness

We have created three separate test projects:

  • Scenario Tests
  • Functional Tests
  • Unit Tests

Lets first talk about the unit tests. Because of how we are using DI, we can now easily test each class individually. Unit Tests are there to help us design new code or pinpoint bugs. These test execute extremely fast and can be run after each build (see: TDDing in Visual Studio). They can help with writing new functionality and refactoring your current code base.

Functional tests use the default Ninject configuration to run use cases against the actual code base. They use the new localdb functionality to make sure each tests runs in complete isolation. It checks if our DI is setup correctly and helps us find bugs at the database schema level (Primary or foreign key errors for example).

Scenario tests are written after a feature is complete. We have just started with this but it looks very promising  We use Selenium to completely automate the browser and execute a complete user scenario. Since those tests are slower, we execute them only once a day and before each release.

Some analysis with Visual Studio 2012

After seeing the session on TechEd about A Modern Architecture Review, I also run the analysis tools that VS2012 offers us.

The tools definitely helped! For example, the Dependency Graph helps with finding dependencies between layers that shouldn’t be there. Another feature, Analyze Solution for Code Clones, helps with finding duplicate code that should be refactored. Calculating Code Metrics shows the quality of the code base and points you to the areas that need some care.

Off course, this is an ongoing process. Now that we have the basic architecture back into shape we can start focusing on individual classes and methods. These tools really help with knowing where to focus.

So, what have we learned

We definitely learned something from this project. The key points  are:

  1. Invest in making your application testable from the start. We shouldn’t have settled for running our ‘Unit Tests’ against the database with the idea that otherwise ‘they wouldn’t test anything’.
  2. Know the difference between Unit Tests and Integration Tests and when to use them.
  3. Separate construction of objects from business logic. Use Dependency Injection to manage the object construction.
  4. Build the simplest thing possible. We shouldn’t have used WCF from the start. It hindered our development and cost to much time.

But now, we have our nice architecture back again. We can make changes more easily and the code base is smaller and better to understand. Off course we still have a lot to do. We want to refactor our Anemic Domain model by moving more and more logic to our actual domain entities and we need to move code from our codebehind files back into the presenter where it belongs.

But for now, we have made quite an improvement to our code base. Code should read like it’s poetry!

Do you have similar stories? Or maybe a whole different view on the topic? I would love to hear it :)