Students and clients approach me too many times asking for my take on whether unit tests should be written before or after the code they test. They often tell me that they’ve read or been told that if they write the unit test “after”, it can’t be considered TDD – Test-Driven Development – which is apparently the goal.
This question betrays a deep misunderstanding of TDD. It is true that historically TDD was mostly done test-first. And most programmers doing TDD today also write test-first. But they don’t write the tests first because TDD somehow demands it. They write them first because TDD works best that way.
Let me rephrase that. Test-Driven Development is not concerned with the types of tests you write or even when you write them. Its primary concern, as the name implies, is driving development. TDD is a programming technique. It’s just another way for programmers to write, well, programs.
This brings us back to the beginning. The question whether the tests should be written “before” or “after” reflects an inability to separate the wheat from the chaff, the important from the trivial. The TDD technique imbues a disciplined approach to software development. The discipline is the wheat, the tests a trivial mechanical extension.
There are different ways to do TDD. Obviously, you can do TDD using unit tests. But you can also use acceptance tests and integration tests. You can use a combination of different types of tests. You might have to test different parts of your application in different ways, due to technological limitations or for other reasons. If you cannot use unit tests, you can usually still do TDD. In fact, you can do TDD using a REPL environment.
Some programmers see adherence to the test-first methodology as necessary for working in a disciplined way. But as we grow adept at TDD, we often find that we don’t need to follow all the strictures of test-first development. We might write a number of related tests all at once, before we write the code, instead of one test at a time. Or we might skip a test or two, if we believe the behavior they describe is trivial, mundane, or implied.
Sometimes we realize that we can avoid writing a number of tests by simply using a better design in our code. This is significant. One of the goals of doing test-first development is to ensure that our code is testable. How can it not be testable, if we first write the test?
So we learn over time which design patterns are more testable, robust and extensible. They often evolve almost on their own. We also learn which structures produce less complexity. And we start using those patterns and structures, by default. We sometimes feel that their role is more boilerplate than logic. The most important take-away is that at this point, many programmers can “see” the tests in their mind’s eye. They can drive the development using virtual, unwritten tests.
My friend and colleague Yuval Mazor calls this “Post-TDD-ism”. The idea is that we are no longer bound to just one way to do TDD. New tools, new techniques, new approaches and languages, as well as experience, all push us forward. We can do Test-Driven Development using different types of tests, and if we are mentally disciplined enough, those tests can be mental. As an interesting corollary, postmodernism started with architecture, as did software design patterns. TDD is about the architecture.
One warning, though. Whether you write the tests before, or after, or not at all, you have to strike a balance. You have to keep other goals in mind. Automated tests help us prevent regression, provide immediate feedback, facilitate better team work, and create a “live” spec that describes our application’s requirements and behavior. When you intentionally neglect to write a test, you have to take those goals into consideration. This is part of the discipline.
So what do you think? Do you always write tests? Do you think there are better reasons to always write the tests first? Or are you a post-TDD-er too?