Noel Rappin Writes Here

Development-Driven Testing

Posted on June 29, 2016

The testing cycle goes from red to green to red to green The testing cycle goes from red to green to red to green

One of Kent Beck’s first articles about unit testing is called Test Infected: Programmers Love Writing Tests. It was written, I think, in 1998. What’s interesting about the Test Infected article is how Kent describes the process: “code a little, test a little, code a little, test a little”.

Which is backwards. Or not-backwards, depending on your perspective, but in any case, it’s not the TDD process that Kent would eventually describe in the XP book, among other places.

I’m going somewhere with this. The question is: what do you gain and what do you lose by writing the tests before the code?

When you write the test first you are specifying the desired result in some detail before you write any code. I think this has two main benefits:

  • By making the desired result clear before you start, you know when you are done. (Or put another way, because you start with a failing test, if the test passes, you know you’ve actually changed some behavior.)
  • In writing the test, you have the chance to create the interface to your code by actually calling it from the test.

I usually find the second benefit more valuable in practice. Writing the test first lets me explore the problem a bit, deciding what parts of the app need to be public and which can be hidden. If writing the test starts to feel burdensome, that often means that the code is not properly structured, and as a result I need to do too much setup for my unit test.

That said, writing tests first is hard.

Sometimes I don’t know enough about the code to figure out what I want the structure to be and I find it easier to do that discovery in code rather than by testing. In particular, if I’m testing against some kind of view output, I find it nearly impossible to determine what I want to test for until I code up at least part of the view. This is often true of other kinds of integration code, I’m just not sure what I’m testing until I see it.

I suspect (but, of course, can’t prove) that it’s not uncommon for a novice to give up on TDD precisely because it can be so hard to write a test for code that doesn’t exist yet.

Writing the code first swaps out the exploration feature of writing tests in exchange for the possibility that writing a useful test is easier.

If you write the code first, you will get most of the benefit of TDD provided you still have some structure:

  • You still work in small steps. You write one method then write the test. Writing the tests is less valuable if you are writing pages and pages of code before you try to test.
  • You still use the test as a check on how good the design of the code is. Instead of exploration, this test that you write after the code is not just validating the result of the code, it’s validating the design. You still need to go back and fix the code if the test is hard to write.
  • You make sure the test can fail, so you still know the test is doing something rather than nothing.

In other words, while “write a small test, then write some code” is still pretty great, “write a small amount of code, then write a small test” has most of the benefit for sometimes a much smaller cost.

Both of them are preferable to “write a lot of code, then write a big, nasty, acceptance test”, or “write a lot of code, don’t test”. Even these might be the best way to go sometimes — I still find automated testing inside a lot of the JavaScript frameworks to be expensive and brittle, but that’s another story for another time.

The problem with coding first is that there’s a natural tendency for the amount of time between tests to get longer and longer. This tends to make the tests harder to write because the more code you write before the test, the more likely you are to insert dependencies or side effects in the code, meaning the test needs more setup or is more brittle.

Overall, though, it’s never a bad time to write a test, as long as the test actually covers code that might fail or change, and if you are willing to use both result of the test and the process of writing the test as information about the quality of your code.


comments powered by Disqus

Copyright 2024 Noel Rappin

All opinions and thoughts expressed or shared in this article or post are my own and are independent of and should not be attributed to my current employer, Chime Financial, Inc., or its subsidiaries.