I’ve been a big believer in unit testing for years now, but was never serious about test-driven development (TDD) until a few months ago. It sounded like an interesting idea, but I didn’t understand why TDD practitioner were so zealous about writing the tests first. Why did it matter? I thought it was to ensure that some project manager doesn’t try to shave some time off the project by cutting the unit tests. Then I started doing some reading about TDD, poking around, asking questions, and trying it out myself. I discovered the real reason for writing tests first is that TDD isn’t about testing code, it’s about designing code. That was my first epiphany. You need to write the tests first because you’re designing how the code works. This isn’t some eggheaded “let’s make pretty UML” diagrams.* We’re actually specifying our object’s API and writing working code. At first our test will fail because the code to implement the behaviour we’re designing hasn’t been written yet. NUnit comes up RED! Now we implement the functionality we just specified.** NUnit comes up GREEN! How does this fit in with the rest of our design? Can we extract commonalities? Can we make our overall design better? REFACTOR! (NUnit should still be green because refactoring shouldn’t change behaviour.) So that’s where we get the TDD mantra: Red-Green-Refactor. When you get into the rhythm, it feels awesome. You’re writing working code quickly and because of your growing suite of unit tests, you have the confidence to make sweeping changes to improve the design.
Next on my list of things to understand was mocking. I had seen it used, but never quite understood the point. Then I downloaded Rhino.Mocks, a very well-respected mock object framework by Oren Eini (aka Ayende Rahien), and decided to take mocking for a spin. Mocking is all about decoupling the class being designed from its dependencies. So if you’re writing a service layer, you can mock out the data layer. The data layer doesn’t even need to exist. You are basically substituting a dynamically generated stub. I had some unit tests that we too closely coupled to the database, which meant they were slow to run and failed miserably if the database wasn’t available. When you’re testing whether your data layer is mapping properly, you need to touch the database, but you can test your presentation, workflow, domain, and even large parts of your data layer without a live database. Unfortunately I couldn’t. Let’s take a look at a test for ensuring that the data layer’s Identity Map is implemented correctly:
[Test]
public void ShouldReturnSameInstanceWhenRetrievingSameDepartment() {
DepartmentRepository repository = new DepartmentRepository();
IList<Department> departments = repository.FindAll();
foreach(Department d1 in departments) {
Assert.IsNotNull(d1);
Department d2 = repository.FindById(d1.Id);
Assert.IsNotNull(d2);
Assert.AreSame(d1, d2);
}
}
I wanted to mock out DepartmentRepository’s dependencies, but I couldn’t get to them. They were internal to DepartmentRepository’s implementation. What to do? Dependency injection to the rescue. Make DepartmentRepository take its dependencies through a constructor (or property setters).
[Test]
public void ShouldReturnSameInstanceWhenRetrievingSameDepartment() {
DepartmentRepository repository = new DepartmentRepository(new DepartmentMapper());
IList<Department> departments = repository.FindAll();
foreach(Department d1 in departments) {
Assert.IsNotNull(d1);
Department d2 = repository.FindById(d1.Id);
Assert.IsNotNull(d2);
Assert.AreSame(d1, d2);
}
}
DepartmentMapper has all the database access logic – TSQL, stored proc names, NHibernate code, or whatever strategy you’re using. DepartmentRepository contains all the database agnostic stuff, such as whether an instance is currently loaded. So now I could mock the DepartmentMapper like so:
[Test]
public void ShouldReturnSameInstanceWhenRetrievingSameDepartment() {
MockRepository mockery = new MockRepository();
IDepartmentMapper mapper = mockery.CreateMock<IDepartmentMapper>();
DepartmentRepository repository = new DepartmentRepository(mapper);
IList<Department> departments = repository.FindAll();
foreach(Department d1 in departments) {
Assert.IsNotNull(d1);
Department d2 = repository.FindById(d1.Id);
Assert.IsNotNull(d2);
Assert.AreSame(d1, d2);
}
mockery.VerifyAll();
}
That still doesn’t do much because I need to tell the mocked mapper to pass back some dummy data. Not too hard to do…
[Test]
public void ShouldReturnSameInstanceWhenRetrievingSameDepartment() {
MockRepository mockery = new MockRepository();
IDepartmentMapper mapper = mockery.CreateMock<IDepartmentMapper>();
IList<Department> mockDepartments = new List<Department>();
for(int i = 0; i < 4; i++) {
mockDepartments.Add(mockery.CreateMock<Department>());
}
Expect.Call(mapper.FindAll()).Return(mockDepartments);
mockery.ReplayAll();
DepartmentRepository repository = new DepartmentRepository(mapper);
IList<Department> departments = repository.FindAll();
foreach(Department d1 in departments) {
Assert.IsNotNull(d1);
Department d2 = repository.FindById(d1.Id);
Assert.IsNotNull(d2);
Assert.AreSame(d1, d2);
}
mockery.VerifyAll();
}
This was feeling all wrong. All I wanted to do was stub out my data layer and I was practically having to “explain” to the mock object the internal implementation of the DepartmentRepository class. I scratched my head for awhile. Left it, came back to it, scratched my head some more. I was missing something about mocking and it felt like something big. Then my second epiphany hit. Unit testing in this way is all about blackbox. You are testing your API. When I call this method with these parameters, I get this result. You don’t care how the method is implemented, just that you get the expected result. Mocking is all about whitebox. You are designing how the method (and hence the class) interacts with its dependencies. You can’t simply drop in mocks to decouple a blackbox unit test from its dependencies. The types of unit tests that you write with and without mocks are fundamentally different and you need both. (If you simply need to decouple your unit tests from their dependencies, you can use stubs, which are objects that return dummy data.) Without mocks, you are writing blackbox tests that specify the inputs and outputs of an opaque API. With mocks, you are writing whitebox tests that specify how a class and its instances interact with its dependencies. A subtle difference, but an important one! So here is the completed, whitebox unit test:
[Test]
public void ShouldReturnSameInstanceWhenRetrievingSameDepartment() {
MockRepository mockery = new MockRepository();
IDepartmentMapper mapper = mockery.CreateMock<IDepartmentMapper>();
IList<IDepartment> mockDepartments = new List<IDepartment>();
for(int i = 0; i < 4; i++) {
IDepartment department = mockery.CreateMock<IDepartment>();
mockDepartments.Add(department);
Expect.Call(department.Id).Return(i);
Expect.Call(mapper.FindById(i)).Return(department);
}
Expect.Call(mapper.FindAll()).Return(mockDepartments);
mockery.ReplayAll();
DepartmentRepository repository = new DepartmentRepository(mapper);
IList<IDepartment> departments = repository.FindAll();
foreach(IDepartment d1 in departments) {
Assert.IsNotNull(d1);
IDepartment d2 = repository.FindById(d1.Id);
Assert.IsNotNull(d2);
Assert.AreSame(d1, d2);
}
mockery.VerifyAll();
}
Let me make a few comments in closing. The MockRepository is usually created in SetUp and mockery.VerifyAll() is called in TearDown. I typically have various helper methods for configuring the mock objects as I use them in multiple unit tests. ReplayAll is important because it switches Rhino.Mocks from record mode (these are the calls you should be expecting and this is how you should respond) to play mode where real calls are being made to the mock objects.
Another thing to note is the heavy use of interfaces because it makes mocking easier. With classes, you can only mock virtual methods/properties because the mocking framework dynamically creates a derived class. (The method/property needs to be virtual so the derived mock object can override the functionality.) This is one reason why TDD code typically makes such heavy use of interfaces. Lastly the use of dependency injection, which is necessary so that we can mock in the first place. You need a way to substitute your mock easily for the real object that it’s replacing. By using TDD with mocking, you have to create a loosely-coupled, easily tested design in order to use mock objects in the first place. Remember, mocking is useful for whitebox testing isolated classes while blackbox testing is useful for verifying the functionality of an API.
* This isn’t to say that UML is worthless. I use it frequently to whiteboard ideas. It’s a good for communicating object structures in common language. I do however think that days or weeks of writing nothing but UML is worthless, a view I didn’t hold a few years ago. The basic problem with UML is that it’s too easy to design something overly complex or completely unworkable. We’ve all had it happen that you start implementing a nice-looking UML diagram and you slap your head saying, “Oops, forgot about that small detail.” Or you realize that what looks like a perfectly reasonable assumption is completely false. TDD values writing working code. Working code tends to quickly identify those gotchas we always run into in software development.
** Jean-Paul Boodhoo does a great job explaining the difference between testing and behaviour specification here. He also mentions a new tool, NSpec, which is very similar in flavour to NUnit, but highlights the behaviour specification aspect of TDD, or behaviour driven design (BDD) as some people are now calling it.