- Introduction
- Part 1: Why use it?
- Part 2: Write My Tests First?
- Part 3: Keys to Effective TDD
- Part 3, Key #1: Write Testable Code
- Part 3, Key #2: Write Good Tests
- Part 3, Key #3: Using Fakes and Dependency Injection
- Part 4: Conclusion
So if you want to test your code, you need to write that code can be tested. Wow, astounding. That’s like saying if you want to score a touchdown you need to get the football in the end zone.
Well, put another way – you need to structure your code in such a way that makes it easier to test. So what does that mean? A few things:
- Separation of Concerns
In order to have an effective unit test, you need to be testing only one thing. That means your code needs to be written so each of the “concerns” are isolated into small, testable chunks. Which brings us to our next point:
- Single Responsibility
This is really an extension of the previous point, but to achieve separation of concerns a good approach is the Single Responsibility Principle (pdf). This states that a class should have one, and only one, reason to change. In other words a class should do only one thing (and do it well!) This means that your ShippingCalculator class shouldn’t calculate shipping and write logging information to a text file. It should call out to a separate Logger class to do that.
- Inversion of Control
Ok, so I’ve separated my concerns into single responsibilities, that’s cool. But now if I want to test my CalculateShipping() method, it will end up calling my Logger class too. Doesn’t that mean I’m really testing more than one thing, which is not what I want to do? Exactly.
So, instead of calling our real Logger class, it would be nice if (during our testing) we called a “fake” class that didn’t do anything. Enter Inversion of Control (IoC) and “Fakes”.
IoC means that instead of a class being responsible for creating the other classes it depends upon, these are provided to it (thus “inverting” the control). This allows us to inject our dependencies into our classes which will allow us to use a “fake” logger when our tests run, and the “real” logger in the production code. (Another common term for IoC is Dependency Injection.)
Let’s take a look at a couple samples.
Class 1: Not very “testable”:
Here are the problems that I see with this class:
- Concerns are not separated. This method is calculating sales tax, logging to a file, and printing and invoice. It’s hard to test only one thing when our method is doing three things.
- This function is performing disk IO and, presumably, connecting to a real database through the OrderRepository class. This can cause our tests to be brittle and slow. Not good!
Let’s take a look at a more testable version:
Class 2: Now that’s what I call testable!
What’s so great about this class? Well, none of the problems identified above are there anymore. We can write tests that will only test this specific function of this class.
Now you might be noticing that this function doesn’t really do anything. That’s right! It’s really more of an aggregator that combines the functionality of other classes to perform some operation.
So how would you test this method? Well I would probably have 4 tests:
- Verify that the FetchOrder() method on my IOrderRepository gets called.
- Verify that the Calculate() method on my ISalesTaxCalculator gets called.
- Verify that the Log() method on my ILogger gets called.
- Verify that the Print() method on my IPrinter gets called.
That’s it. We’re testing what this function does, which is call other functions. So, assuming that we have other tests that will test each of those other functions, this PrintInvoice() method is totally covered by unit tests.
0 comments:
Post a Comment