Sunday, February 7, 2010

TDD adapted for mere mortals

TDD adapted for mere mortals

I've been teaching and practicing agile for several years and there is definitely a problem with TDD: People find it very difficult to use. I believe there are certain points, either in the TDD itself or in people's interpretation of it, that should adapted (at least for mere mortals):

Writing test before code

It is definitely a very good practice to interweave coding and testing. This is what we programmers want to do; we feel the urge to test run a certain piece of code as it feels complicated. However, writing the test before code is not a natural way in many cases. For example, let's consider a BookService class. You'd like to implement its borrow(String borrowerId, String callNo) method. If you insists on writing the test first, you'll have to think very hard what collaborators the BookService object will use. It is not only difficult, but most likely incorrect. A much more effective way is to write the borrow() method first, then you can see what collaborators it needs (e.g., a BookDAO, a BorrowerDAO, a system clock and etc).

Most TDD demos don't have this problem because they work on classes that need no collaborators, for example, stacks, calculators.

Note that I am not advocating writing the complete code before writing the test; we should build the functionality in suitable steps. For mere mortals, try to implement the basic functionality first, then test it, then write more code and then more test.

My suggestion is to replace "writing test before code" with "interweaving coding and testing".

Take the smallest step that makes the test pass

I agree that we shouldn't write too much code without test-running the code. If we do, it's difficult to isolate the bug. But why always take the smallest step if we are pretty sure that it is going to work? The size of the step depends on the complexity of the code. We shouldn't take too large a step (hard to isolate a bug), nor too small a step (waste of time).

The whole idea is a well-established principle in testing: Risk-based testing. That is, we should put more effort on testing high-risk code, and less on low-risk code. Programmer's effort is the most scarce resource in software development projects. So, we should prioritize its use wisely.

My suggestion is to replace "take the smallest step possible" with "take the smallest step before you're worried with the correctness of the code".

If you aren't doing TDD, you aren't professional

This is not defined in TDD, but many people believe it. I think this is against the agile manifesto which says we should value people over process. Forcing TDD into people's throats is exactly the opposite. If people have tried but it doesn't help them, they will simply not use it. It's that simple. People should have every right to use whatever works best for them.

In fact, most programmers like testing: they feel the urge to test run the code if it gets complicated. It's just that writing the test before code is so difficult and against their nature. Therefore, our process should work with their nature, not against it.

My suggestion is to replace "every professional programmer should do TDD" with "every professional programmer should keep looking for their own best practices".

TDD helps you design the API of your code

This doesn't make much sense to me at all. The user requirement guides you in implementing your UI classes. When implementing the UI classes, you are guided to design the API of your service classes. When implementing your service classes, you are guided to design the API of your DAO classes.

The real design aspect of TDD is not about the design of the API, but to make sure your code is loosely-coupled and thus is easy to test.

My suggestion is to replace "TDD helps you design the API" with "testing helps make your code loosely-coupled".

 

1 comment:

  1. In your first point I think you're missing the point entirely.
    You do not write a test for the whole borrow method, you write a test for one thing it does, then you make that test pass, then you refactor. Then you write a test for the next bit.
    For a great example of this I highly recommend Steve Freeman and Nat Pryce's 'Growing Object-Oriented Software Guided by Tests'. This works through examples of using dependencies and writing OO code with testing.
    Point 2: I tend to agree, but it is definitely worth doing some work, especially when starting out, like this. Write the smallest test, then the smallest amount of code to pass it. Then always, always, always, refactor.
    Point 3: Personally I think it is unprofessional not to be practicing TDD to some extent, but I do get the point and your replacement is perhaps a more important point - to all developers, TDD practicing or otherwise.
    Point 4: Agreed. TDD helps you define interfaces, but not necessarily APIs.
    For me, TDD has the great advantage of helping me clarify my thoughts and the intention of the code. Its not just about reducing bugs, its also about creating clear code and incresing communication with other deveolpers. I find myself reading test cases in parallel with code to get a clearer idea of what the code is doing. If there's no test case, I often try to write some.

    ReplyDelete