Saturday, July 3, 2010

The 4 moves of TDD

The three laws of TDD are:

  • Write no production code without a failing test
  • Write just enough of a test to fail
  • Write just enough production code to get the test to pass

This list doesn't include refactoring, which is typically an assumed activity. In fact, some people refer to these rules as "red, green, refactor". An even older version of this, from the Smalltalk community, is Red, Green, Blue.

So there are two kinds of code: test & production. There are two kinds of activity: writing & refactoring. Interestingly, at one level it is all code. The thing that distinguishes both sets is intent.

The intent of a test is to demonstrate or maybe specify behavior. The intent of production code is to implement (hopefully) business-relevant functionality.

The intent of writing code is creation. The intent of refactoring code is to change (hopefully improve) its structure without changing its behavior (this is oversimplified but essentially correct).

If you mix those combinations you have:
  • Writing a test
  • Writing production code
  • Refactoring a test
  • Refactoring production code
  • An important behavior to practice is doing only one of these at a time. That is, when you are writing tests, don't also write production code. Sure, you might use tools to stub out missing methods and classes, but the heart of what you are doing is writing a test. Finish that train of thought before focusing on writing production code.

    On the other hand, if you are refactoring production code, do just that. Don't change tests at the same time, try to only do one refactoring at a time, etc.

    Why?

    First an analogy that almost always misses since most developers don't additionally rock climb.

    When rock climbing, a good general bit of advice is to only move one contact point at a time. If you just consider two hands and two feet, that suggests that if, for example, you move your right hand, then leave your left hand and both feet in place.

    This gives you stability, a chance to easily recover by simply moving the most recent appendage back in place and, when the inevitable happens, another appendage slips, you have a better chance of not eating rock face. If you move more than one thing at a time, you are in more danger because you've taken a risky action and reduced the number of points of contact, or stability.

    Will you sometimes move multiple appendages? Sure. But not as a habit. Sometimes you need to take risks. The rock face may not always offer up movement patterns that make applying this recommendation possible. Since you know the environment will occasionally work against you, you need to maintain some slack.

    Practicing Test Driven Development is similar. If you change production code and tests at the same time, what happens if a test fails? What is wrong? The production code, the test, both, neither? An even more subtle problem is that tests pass but the test is fragile or heavily implementation-dependent. While not necessarily an immediate threat, it represents design debt that will eventually cause problems. (This also happens frequently when tests are written after the production code as it's seductively easy to write tests that exercise code, expressing the production's code implementation but fundamentally hiding the intent.)

    Notice, if you had only refactored code, then you know the problem is in one place. When you change both, the problem space actually goes from 1 to 3 (4 if you allow for neither). Furthermore, if you are changing both production and test code at the same time and you get to a point where you've entered a bottomless pit, you'll end up throwing away more work if you choose to restore from the repository.

    Are there going to be times when you change both? Sure. Sometimes you may not see a clear path that gives you the option to do only one thing at a given time. Sometimes the tests and code will work against you. Often, you'll be working in a legacy code base where there are no tests. Given that the environment will occasionally (or frequently) work against you, you need to maintain some slack.

    Essentially, be focused on a single goal at any given time: write a test. then get it to pass. clean up production code & keep the tests first unchanging and then passing.

    I find that this is a hard thing both to learn and to apply. I frequently jump ahead of myself. Unfortunately I'm "lucky" enough when I do jump ahead that when I fail, I thoroughly fall flat on my face.

    This approach is contextual (aren't they all?). Every time you start working on code, you'll be faced with these four possibilities. Each time you are, you need to figure out what is the most important thing in the moment, and do that one thing. Once you've taken care of the most important thing, you may have just promoted the second most important thing to first place. Even so, reassess. What is the most important thing now? Do that.

    Good luck trying to apply this idea to your development work. I'm interesting in hearing about it.

    No comments:

    Post a Comment