Thursday, April 24, 2008

Cleaning up our test code

In the last posts about testing in a TDD fashion, I tried to dig into the reasons why many developers tend to write Soap Opera tests which end up being less effective and maintainable that they should be. As I said earlier, test share a common logical structure, I stuck to this one:

  1. Set up
  2. Declare expected results
  3. Exercise the unit under test
  4. Get the actual result
  5. Assert that actual result match the expected results

In the popular essay Mocks Aren't Stubs, Martin Fowler uses a differently grained structure (setup, exercise, verify, teardown) but I'll stick on the original structure, at least for now.

Since "refactor" is the weaker step of the TDD mantra, I tend to keep a slightly different approach, trying to think at my tests in the cleanest possible way. There is normally not so much to do with step three – which is often no more than a single method call – but often a lot can be done to improve all the other phases.

Setting up the test

JUnit structurally poses a separation between the common set up shared by a family of test and the specific set up or initialization needed for a single test to run. The former is placed in the setUp() method, while the latter is generally the beginning of our test method. A common situation is to place here the definition of all the Domain Objects needed to perform the operation to be tested. This is also a good place to check if your application has been properly designed. Creating objects for testing purposes should be a straightforward activity. As a rule of thumb, creation of the necessary objects shouldn't need more than three-four lines of code. Does it sound thin? Let me explain.

Objects should be ideally created one-shot. In other words, you should have robust constructors available to create the domain objects without having to bother with setter methods. This might not be a completely viable options if you have a lot of optional attributes in your entities, which probably shouldn't be in you constructors anyway. You are definitely luckier if your architecture already has some Factory in place (such as the ones prescribed by DDD, by the way).

Complex object structures should be available off-the shelf: if I need to test Entity A, associated with B and a specific C instance, and this is a boundary condition for my application, I want this combination to be readily available for any single test that pops out in my brainless mind. Ok, you can achieve a similar result with cut-&-paste, but … please … (ok, the official reason is that you end up slowing test development and increasing unnecessary coupling). An off-the-shelf test object approach fits particularly well with the agile style of personalizing users and typical entities: if I am developing a banking system and Fred is my average user with a banking account and a VISA, while Randolph and Mortimer are very rich people with many accounts investment and so on, I want to have available in my test framework something like createFred() and createRandolph() or createMortimer(), to be used in many more short tests. Such convenience methods are particularly useful when business functions or business objects are complex, and such complexity ends up preventing people from writing the test they should write.

The worst case scenario you might have to deal with, happens when you have only the Java Bean empty constructor and a plethora of setter methods. Setting up complex objects will take pages, and the code is a perfect humus for small bugs in the test definition. In addition, I will hate you :-). In general, test code might just be as buggy as production code, so writing the shortest possible test sounds like a good advice, both from a robustness and readability point of view. In general, creating objects for testing greatly benefits from the presence of dedicated Factories, and this should be taken into account when designing the application. Creating objects should be easy because we want to do that over and over and over.

In Java, Spring helps a lot in managing creation of other type of objects, such as Services, Controllers, Managers or DAOs. After all, Spring is like a "pervasive factory" that takes care of object setup and initialization. Typically, services are Spring Managed, while entities are not. So we have to deal with entities by ourselves. If Factories are not available, I often end up writing factories for test specific purposes. Depending on the level of control I have over the application they often make it for a refactoring on production code as well. If factories are already in place, we can subclass or wrap them with a test-layer factory class that provides us with the aforementioned convenience methods


1 comment:

Anonymous said...

Good words.