Towards a Theory of Test-Driven Development

Red, Green, RefactorThis post examines how well we really understand the practice of Test-Driven Development (TDD).

Red, Green, Refactor

By now we all know that Test-Driven Development (TDD) follows a simple cycle consisting of these steps:

  1. Start by writing a test. Since there is no code, it will fail (Red)
  2. Write just enough code to make the test pass (Green)
  3. Clean up the code (Refactor)

The beauty of this division is that we can focus on one thing at a time.

Specify, Transform, Refactor

Although simple, TDD isn’t easy. To execute the TDD cycle well, we need a deeper understanding that we can only get from experience.

For instance, after doing TDD for a while we may look at the steps as:

  1. Specify new required functionality
  2. Improve the functionality while keeping the design constant
  3. Improve the design while keeping the functionality constant

When we look at the TDD cycle in this light, we see that the Green and Refactor phases are each others opposite.

Refactorings and Transformations

In the Refactor phase, we use Martin Fowler‘s refactorings to clean up the code.

TransformationRefactorings are standard alterations of the code that change its internal structure without changing its external behavior.

Now, if the Green and Refactor phases are each others opposite, then you might think that there are “opposite refactorings” as well. You would be right.

Robert Martin‘s transformations are standard alterations of the code that change its external behavior without changing its internal structure.

Automated Transformations?

Most of us use powerful IDEs to write our code. These IDEs support refactorings, which means that they can do the code alteration for you in a manner that is guaranteed to be safe.

So do we need something similar for transformations? I think not.

Some transformations are so simple in terms of the changes to code, that it wouldn’t actually save any effort to automate them. I don’t see a lot of room for improving the change from if to while, for instance.

Other transformations simply have an unspecified effect. For example, how would you automate the statement->statements transformation?

RefactoringThe crux is that refactorings keep the external behavior the same, and the tools depend on that to properly implement the refactorings. However, transformations don’t share that property.

Standardized Work

In the Specify/Transform/Refactor view of TDD, we write our programs by alternating between adding tests, applying transformations, and applying refactorings.

In other words, if we look at the evolution of our non-test code through a series of diffs, then each diff shows either a transformation or a refactoring.

It seems we are getting closer to the Lean principle of Standardized Work.

What’s still missing, however, is a deeper insight into the Red/Specify phase.

How to Write Tests

The essential part of the Red/Specify phase is obviously to write a test. But how do we do that?

For starters, how do we select the next test to implement?

Unit test failureThere is almost always more than one test to write for a given requirement.

And the order in which you introduce tests makes a difference for the implementation.

But there is very little advice on how to pick the next test, and this is sorely needed.

Kent Beck has a kata for experimenting with test order, which helps in gaining understanding. But that’s a far cry from a well-developed theory like we have for refactorings.

So what do you think? If we understood this phase better, could we come up with the test writing equivalent of transformations and refactorings?

Please share your thoughts in the comments.


7 thoughts on “Towards a Theory of Test-Driven Development

  1. TPP advises that the next test is the one that requires the simplest possible change to the existing code. But I have more questions:
    Assume you have a class and an API, and you are about to start TFDing the first method defined by that API. For ease of understanding, let’s assume that each method must meet a number of micro-requirements, and that each test will examine one of these micro-requirements. Micro-requirements are derived from, and dependent on, the API, but they are not explicitly stated.
    • How do you decide *what* to test? [I.e. how do you identify micro-requirements?]
    • How do you decide *what the test will check* in order to confirm the micro-requirement you’re trying to test? [Return value; persistent class variables; calling an out-of-class method; ….]

    1. You can get the micro-requirements from the requirements. IOW, what do you need next to make the (next part of the) BDD test work?

      1. Aren’t “the requirements” at too high a level to derive one method’s micro-requirements from them? The API you’ve been given is part of the implementation; you’re just extending it down to code/method level. [IOW, I’m not sure I understand your answer. ;-)]

        1. We shouldn’t write any code that doesn’t serve a purpose, i.e. that isn’t necessary to meet some requirement. Of course, there are many possible implementations for a given requirement, so the requirements don’t always dictate exactly how a method should behave. You can use tests to further restrict the method, but you have to be careful not to overdo it. Such tests make it very difficult to swap an implementation out for a different one. So you end up with a hierarchy of tests, with the end-to-end tests representing the requirements and lower-level tests representing the chosen design.

  2. Well writing Some excellent points here as a developer i want to tell here the essential part of the Red is obviously to write a test.

Please Join the Discussion

Please log in using one of these methods to post your comment: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s