Tuesday, October 5, 2010

Dependency Injection Part 4: Tests

In my last post, we identified that the SimulationRunner class violated the Single Responsibility Principle by concerning itself with:
  • Setting up our application, (in fact SimulationRunner created an instance of every class in the application)
  • Running our application (via simulator.Run())
(In case you've forgotten, the Single Responsibility Principle is the assertion that there should never be more than one reason for a class to change). We addressed the disparate responsibilities in SimulationRunner by creating the ContainerSetup class and leveraging Autofac (a badass Dependency Injection framework) to create and wire-in instances of our dependencies for us (i.e. setup our application).

We've arrived at code which is easy to change. That's valuable for our requirements will certainly change. There's only one rather large lingering concern:

I'm not sure it works

As our code stands now, I've no justification for believing that our application will work correctly in every circumstance. If you're sure, then you're new to this and new developers seem to know everything :) For us to be sure our code is solid, we need evidence, but why gather evidence if you're certain this application won't break? See the problem? We'll gather our evidence via a suite of simple Unit Tests.

Unit Tests

A Unit Test is a simple test of a small unit of code, for our purposes, units will be single methods. Remember our ConsoleInputService class?Here's a simple set of unit tests for ConsoleInputService. We're testing the following cases below:
  • The ConsoleInputService.GetInput() accurately returns console input
  • The ConsoleInputService.ExitWasRequested() returns true when it reads a new-line from console input
  • The ConsoleInputService.ExitWasRequested() returns true when it reads empty input from the console
  • The ConsoleInputService.ExitWasRequested() returns true when it reads a new-line from console input
  • The ConsoleInputService.ExitWasRequested() returns true when it reads a string containing only spaces from console input
The ConsoleInputMock class below is a simple test helper which simulates a given string coming from the console as input. Often to isolate a single class to test its methods, you have to do sneaky things like this.
As a general guideline, unit tests should be self-contained, clearly named, and as simple as possible. They should also only test one thing. This is important for when a test fails, its helpful to know exactly what broke. Have another look at the methods in ConsoleInputServiceTests above, you should be able to see what they're testing based on their names (e.g. ExitIsRequestedOnEmptyInput). Speaking of testing, here's a screen-shot of the output of the tests in ConsoleInputServiceTests, see the failure?
ExitIsRequestedOnWhitespaceInput failed. When ConsoleInputService.ExitWasRequested() encountered input that was just spaces, we expected it to return true, signalling that the user wished to exit. It evidently sees that as valid input. Let's fix ConsoleInputService.GetInput() to trim all input and re-run ConsoleInputServiceTests.We can now re-run our ConsoleInputServiceTests and we pass.
Here are a few tests of the MessageService and ConsoleOutputService classes. Here's the source code for this post as well, to run it, make sure you grab a copy of xunit, unpack it somewhere meaningful like C:\Program Files\xunit-1.6.1 then goto the Properties of the Pass4Tests project and in the Debug section set Start external program: to point to one of the xunit.console executables. Your setup should look similar to this:

Up Next

In the next post we'll tackle unit testing the Magic8BallSimulator class which is more interesting since it contains 3 dependencies.

Happy testing.

0 comments: