Monday, 04 December 2006
There has been extensive discussion on the Test-Driven Development Yahoo group recently about state-based testing, interaction-based testing, and the use of Mocks and other Test Doubles. I tend to use quite a lot of test doubles in my tests, so I thought I'd explain why.
I find that using various forms of test doubles whilst doing TDD can greatly speed my development, as it allows me to focus on the class under test, and its interaction with the rest of the system.
I tend to use a test double for any part that's not directly related to the responsibility of the class under test. For example, if I'm testing something that needs to send an email, then I will use a dummy smtp client, which just logs the email for examination in the test, rather than actually sending it. If I need a class to encrypt something, I will provide a dummy encryption algorithm, and verify that the class under test encrypts the correct data, and correctly uses the encrypted output, rather than using a real encryption algorithm, which may produce output with an element of randomness, and which cannot therefore be readily used in an assertion.
Not only does this make testing easier, since it provides for greater isolation, and more focused tests, but the resultant separation of concerns is good for the overall design — rather than relying on a particular concrete implementation, the class under test now relies on an abstract interface. This reduces coupling, and increases cohesion. It also makes it easier to reuse code — lots of small classes, with well-defined responsibilities, are much more likely to be useful elsewhere.
Though I would tend to call my test implementations of these interfaces "mocks", the term "Mock" has come to mean a quite specific type of implementation, where the user sets "expectations" on which member functions will be called, with which parameters, and in what order; the Mock then verifies these expectations, and asserts if they are not met. Many "Mock Objects" are also automatically derived, commonly by using reflection and fancy "Mock Object Frameworks". My test implementations rarely do these things, and are probably better described as "Stubs", "Fakes", or even something else. I'm coming to like "dummy" as in "Crash Test Dummy" — it's not a crash that we're testing, but the "dummy" does provide information about how the class-under-test behaved, and allows the test to specify responses to stimuli from the class-under-test, and therefore exercise particular code paths in the class-under-test.
Going back to my sending-email example above — by using a dummy implementation, the tests can check the behaviour of the class under test when the email is sent successfully, and when it is not. The test can also verify that it is sent at all, and with the correct contents.
Furthermore, I generally don't write a separate class for the dummy implementations — I use the "Self Shunt" pattern, and make the test-case class server double-duty as the dummy implementation. This has the added benefit that I don't have to explicitly pull any data out of the dummy class, as it's already right there in the test — the dummy functions can just store data directly in the member variables for the tests to use.
The real question, as ever, is where to draw the line between using real code, and providing dummy implementations; the extremes are easy, it's the in-between cases that require more thought. If the code talks to an external system (remote server, database, filesystem, etc), then for TDD-style tests, it's probably best to provide a dummy implementation. Likewise, at the other extreme, you have to have a real implementation of something in order for the test to be worthwhile.
I tend to draw the line along where I think the division in responsibility lies — if the code needs to send an email in response to certain conditions, then there are two responsibilities: sending an email, and making the decision to do so based on the conditions. I would therefore have two classes, and two sets of tests. One class will actually unconditionally send an email, in which case I would provide a dummy implementation of an SMTP server under control of the tests, and have the class under test connect to it as-if it was a real SMTP server. In this case, the dummy will have to implement the full SMTP protocol, though the responses might be hard-coded, or depend on what aspect is being tested.
The second responsibility (deciding to send an email) belongs in a separate class, and the tests for this would provide a dummy implementation of the email-sending interface, so there's no network traffic required. I would (and have done where this has been required) probably develop this class first, in order to isolate precisely what interface is needed for the email-sending class, unless I already had an email-sending class that I was hoping to reuse, in which case I would start with the interface to that as it stood, and refactor if necessary.