Hello! Today, we will talk about Test-Driven Development. To be honest, I was torn between talking about TDD and not talking about it. The reason is that I personally don’t use it very often. Yet, just because I don’t follow a practice doesn’t mean I can’t talk about it. The Coder Cafe isn’t Wikipedia; I share insights and opinions, and you’re free to disagree.
Test-Driven Development (TDD) is a software development approach where tests must be written before the actual code. The process follows the Red-Green-Refactor cycle:
Red: Write a test before coding and confirm it fails (since no code exists yet).
Green: Write just enough code to make the test pass.
Refactor: Improve code structure while keeping the test passing.
For example, if we had to write an array sorting function using TDD:
Red: Start with tests focusing on the main and edge cases.
Green: We write the sorting function to make the tests green.
Refactor: Tweak and refine our implementation and validate that the tests are still green.
What I find interesting about TDD:
Thinking about primary behavior and edge cases upfront can sometimes be valuable, as it helps ensure we aren’t biased by the code we just wrote. This approach works particularly well with utility functions, such as the earlier sorting example. When the API and behavior are clear, we can anticipate what cases should be tested, such as:
An unsorted array
An array already sorted
An array with one element
An empty array
Furthermore, I really appreciate the Green-Refactor part of the cycle. I use it extensively, as refactoring my code after my tests are green helps me spot potential issues early on.
Now it’s time to jump into what I dislike about TDD.
When I have to write some code, be it at Google or for personal projects, I rarely start with a clear API in mind. Instead, it’s the different iterations of my code that help me clarify important questions that impact unit tests, such as what data I will expose and what the expected behavior is. Often, there are too many unknowns, like which data sources to use or which external APIs or services I need to rely on.
My typical process is rather to start coding and then refining my understanding of the data and behavior I need to expose during development. It’s only once these questions are clear that I can start writing unit tests.
So, I could have stopped here. I have already mentioned what I liked and disliked about TDD. Yet, as the industry didn’t stop there, I won’t stop either.
Today, this is the kind of post we can find on LinkedIn:
When I see this, I think our industry is completely wrong. I mean, TDD as a requirement for “delivering top-quality code”? Seriously?
Think about reviewing code in a pull request. What do we look for in the tests?
Do they really validate the correctness of the code?
Are they isolated and readable?
Can we maintain them easily?
Etc.
NOTE: We discussed unit test properties in #7: 10 Unit Test Properties.
Yet, do we ask ourselves whether the person used TDD? Of course not. Because TDD is a personal choice, not a measure of test quality.
Just as we don’t judge the quality of a code on the coder’s age, country, or education, we shouldn’t judge tests based on whether TDD was used. What matters is the quality of the tests, period.
This is the reason why I think so much TDD content is plain wrong. Do you want to assess a candidate's knowledge of tests? Talk about unit test best practices, test organization, coverage, etc. Whether someone uses TDD says nothing about their testing skills.
If TDD helps you write good tests, that’s great—use it! And it’s fine to advocate for it. But if you can write effective unit tests without TDD, just don’t use it. TDD is a matter of preference, and no one should be judged on whether they use it or not.
It’s been a while since I wanted to share my opinion on TDD and explain why I think the industry is wrong about it. Please let me know in the comments what you think about my opinion. Tomorrow, we will discuss logic in tests.
“ Red: Start with tests focusing on the main and edge cases.
Green: We write the sorting function to make the tests green.”
Sounds like you’re writing multiple tests before making any pass. While this doesn’t seem like a bad workflow it’s not TDD. Following TDD you’d write one test, make it pass, then write the next test.
Your point about evolving APIs is well taken. By only writing one test at a time you reduce the amount of work retrofitting new APIs.
I agree with you Teiva, especially about the point in API that is still changing while you code. I had tried TDD and I would say that it depends on the changes we are making. Like any tool / methodology, figure out if it’s a good fit before blindly apply it to your current situation.