I recently spoke with a software tester and couldn’t help but ask for his opinion of test-driven development (TDD) since it’s a mandated practice here at Qumulo for our engineering teams.
To paraphrase his sentiments, he said:
I think unit testing is important, but developers don’t often think from a user’s perspective when writing their tests. Instead they think about the implementation, and how they expect it to work, which isn’t often how it actually works.
His bias as a tester isn’t lost on me, but I agree that someone, whose job is strictly to write functional tests, can bring a much-needed mindset to the release cycle. I also think it’s important for feature developers to share that breaker mindset, because software engineers are designers first and coders second. We need to think about use cases, how things can go wrong, and how to mitigate the damage when things inevitably do go wrong.
At Qumulo, we’re continuously crafting an onboarding and growth process for our engineers that strengthens those skills, which includes the use of TDD to facilitate that growth process.
An engineer is really just a scientist trying to solve problems for customers. In my experience, TDD’s most valuable contribution is how it facilitates the scientific method for engineers that practice it. When you’re writing a unit test, you’re making a hypothesis that if, and only if, you change the production code, you’ll satisfy the properties asserted by your newest test. This is actually two hypotheses compounded:
- The existing production code won’t satisfy the properties asserted by your new test.
- The modified production code will satisfy the properties asserted by your new test.
Without TDD, you’re practicing bad science by forgetting to perform your experiment on a control population. It may be the case that your test would have passed without changing the production code, and this means that either your test has a bug, or it’s not communicating any new properties of the production code.
Qumulo’s code base is like a lab notebook; we have more than 55,000 tests that document all of the experiments performed on the production code. It communicates what we know about the code, and each test is only as valuable as what it communicates to the reader. It may be the case that our understanding of the customer’s needs have changed, and as such, so should the code and what we know about it. It’s up to the engineers to decide whether a test is valuable, and if not, it’s deleted.
By facilitating the scientific method, TDD allows engineers to focus on a more troubling problem: what tests are we missing? This is the core challenge of the design process, because it requires engineers to get inside their customers’ heads. It forces them to consider who are our customers? How will they exercise our code? (Note that “customers” can include other engineers that use your code.)
And the design process is always collaborative, because it’s dangerous to go alone!
“What one programmer can do in one month, two programmers can do in two months.” — Fred Brooks
I’m taking Fred’s words out of context here, but if “what one programmer can do” is write bugs, then Fred’s statement takes on a new meaning: two programmers will write as many bugs in two months as one programmer will write in one month! But seriously, when you practice TDD, every bug you write is actually a test you didn’t write, and two engineers pair programming will think of more test cases than one engineer. It also tends to produce cleaner code with better variable and function names. In general, discipline is stronger with each person holding the other accountable.
So we recommend that engineers pair by default, and that doesn’t stop at coding. At Qumulo, we write user stories, write tasks, and solve problems on a whiteboard in pairs. Pairing doesn’t just reduce the rate of bugs, it spreads information about the problem domains, and (as a bonus) it’s fun.
It’s fun if you do it right. So how do we teach our engineers to test and pair effectively?
We recently added an event to our onboarding process called the TDD and Pairing Workshop. We give presentations on TDD and pairing, discuss the benefits and pitfalls, and practice everything we learned by pairing on a kata (a small practice problem, intended to be repeated frequently). We use the ping-pong, driver/navigator, and mobbing styles to practice strict TDD and implement a library for converting roman numerals to decimals and vice-versa.
Participants often make the same complaints about pairing and TDD:
- I’m uncomfortable coding with someone watching; it makes it hard to think clearly.
- I often get stuck in a disagreement with my partner and waste time.
- I felt like I was just a typing machine at the mercy of the navigator; I didn’t keep up with the problem we were solving.
- I feel like my partner is getting distracted while I do all the work.
- TDD is inefficient; I end up writing inelegant code and fixing it later.
We’ve been practicing these aspects of extreme programming (XP) for years at Qumulo, so we’ve addressed these problems and offer the following lessons learned to new hires:
- Set clear, achievable goals and operating procedures for your pairing session before you start.
- If you notice productivity declining, take a break.
- If you haven’t had much experience pairing, it can be exhausting. Let your partner know, and you can shift the discourse to something less intense.
- If you can’t move past a disagreement, bring in a third party to mediate.
- If you’re losing track, tell your partner you want to switch roles or styles to slow things down. It’s not good be left behind.
- Part of TDD is writing the simplest code possible at that moment.You often need to identify an abstraction and refactor (see Transformation Priority Premise).
In addition to the workshop, pairing and TDD are generally encouraged by our engineering teams, and each team has a technical coach whose goal is to communicate coding standards and help team members find designs and code factorings that are testable.
It’s easy to be skeptical of XP if you haven’t practiced it long enough to experience the benefits. We find that TDD and pairing help engineers grow into skilled scientists and designers that communicate more effectively and leave their egos at home. You might be tempted to claim glory for crafting a beautiful snowflake of code by yourself, but by pairing, you’re forced to the realization that code is for everyone to design, read, and re-use, so it doesn’t make sense to attach it to your personal pride.