Make it easier for your team to align.
Test-Driven Development (TDD) is hard. Crafting a good test suite is hard. And even worse, there’s many different TDD “schools” vying for dominance: London, Detroit, Discovery Testing, Property Testing, Outside-In BDD, Inside-Out TDD, etc.
It’s all too easy to get sucked into a battle with a fellow developer over which school is “right.” But, of course, none of them are “right.” They’re all tools and techniques that we can use to test-drive software.
Whenever you’re on a team with people who have opposing viewpoints on how to TDD, I recommend starting by reminding everyone of the saying: “Everyone believes their way is the right way, and everybody is wrong.” There’s probably no truer statement in software engineering.
Instead of arguing about which way to TDD, everyone on your team should step back and talk about their goals for the test suite. Here are some goals I recommend:
There are plenty of other goals we could discuss as well, but these four are foundational and will serve as a good starting point. Let’s break them down.
A test suite should be fast, for the simple reason that if it’s slow, you’re less likely to run it. And the less likely you are to run it, the longer it takes for you to know if you’ve broken anything while refactoring. And the longer that takes, the less likely you’ll be to refactor.
How fast should a test suite be? I asked a team recently, and got several answers:
- “Short enough that your mind doesn’t wander.”
- “Short enough that it’s not an excuse to take a break.”
- “Less than the time it takes to get a cup of coffee.”
While these answers are good, I prefer the simple goal of “instantaneous.” The ideal test suite time is zero seconds.
We write tests to help us keep our production code clean so that we can go fast forever. But test suite code is susceptible to all of the same problems that plague production code. No one wants an unreadable, unmaintainable test suite. For example, without proper grooming, you can quickly end up with a test suite that’s riddled with knowledge duplication. And that knowledge duplication leads to fragility — a simple change you’d like to make to the production code causes tons of tests to break, and each test has to be fixed individually.
No one wants an unreadable, unmaintainable test suite.
You have to keep your test suite clean (i.e., clear, readable, and maintainable) — not just the production code. Otherwise, you won’t be able to maintain your test suite over time. It will become such a burden that team members will stop writing tests.
If your test suite is green, how confident should you be that the software works? Obviously, the ideal answer is: 100% confident! If my product manager asks me “Can we ship the software”, I want to be able to point to a green CI build and say “Yep — it’s green. There’s nothing more I need to do to know that we have a working release. Ship it!”
Even if you’ve been practicing TDD for a while, you may have been wondering what I meant by this. A test suite should give you freedom — the freedom to refactor your code! This may sound obvious, but actually, it’s not uncommon to discover teams have written test suites that make it harder for them to refactor their production code, instead of easier. Sometimes it’s because they’ve tested implementation, instead of behavior. Sometimes it’s because they’ve coupled their tests to the underlying design of their production code, making it hard to refactor that design, since the tests are all aware of it. (This problem often stems from a common, but wrong, definition of “unit testing”: they mistakenly believe that unit testing means that for every class, and every public method of every class, there must be a corresponding unit test).
The Great Balancing Act
Imagine it: a test suite that runs instantaneously, gives you 100% confidence that the software works, is incredibly readable and maintainable, and gives you unfettered freedom to refactor your production code. That would be a fearsome thing to behold!
But actually, it’s hard to balance all of these qualities. Each test you write will take a little bit of time to run; even if each individual test only takes a few milliseconds, they’ll add up. And you might very well discover that in order to feel really confident in your test suite, you’ll need to write an end–to–end integration test or two. Which will slow your test suite down even more. But then maybe you’ll decide that to speed it up, you’ll use test doubles to “mock everything out” — so many teams have practically ruined their test suites by overzealously “mocking” everything out in the pursuit of speed. The test suite runs fast — but they’re not confident that it’s actually testing anything real anymore. And the tests themselves are tightly coupled to the underlying implementations, making it harder to refactor.
As you and your team are practicing TDD, you should constantly ask yourself the following questions:
- Is this test helping us meet our test suite goals of Fast, Freedom, Clean, and Confidence?
- Or are we testing in such a way that it undermines one or more of our goals?
- And if so, is there a different way we could test this?
These kind of objective goals can lead to less heated, more reasoned discussions about different ways that you could TDD. Aligning a team around goals leads to less religious debates and more productive conversations, experimentation, and innovation.
An Aside On Goals
I stated that all of these are goals. “Goal” itself is another word in our industry that gets bandied about, but often misapplied. I was guilty of that particular sin for years, until one my fellow pivots Graham Siener (who built the product management practice at Pivotal Labs) gave me the following definition: a goal is something you would never say the opposite of.
For example, “Fast” is a goal. You would never say the opposite: “I want my test suite to be slow.” That would be absurd! It’s the same for all of the other goals we’ve listed. You’d never strive for a test suite that doesn’t give you confidence, makes it harder to refactor, or that is hard to read and maintain. By applying this definition, you make it easier for teams to align on goals, and can have more rational discussions about the pros and cons of various strategies the team could choose to achieve their goals.
Thanks To: Jonathan Sharpe, Mike Gehard, Desmond Rawls, Augustus Lidaka, Adam Berlin, Xing Zhou, and everyone else at Pivotal that provided thoughts and feedback on this post.
Change is the only constant, so individuals, institutions, and businesses must be Built to Adapt. At Pivotal, we believe change should be expected, embraced, and incorporated continuously through development and innovation, because good software is never finished.