Exploring TDD: Not about productivity or design
Explore how a team's cognitive load, habits and discipline affect adoption and experimentation
🎉 We just passed 1000 subscribers 🎉 A huge welcome to the recent joiners.
I struggled with my impostor for a decade before devoting time to writing. I can’t tell you enough how much it means to me that you’re committed to sharing some of your time and focus to read my content and engage in conversation.
I believe we have
to thank who started recommending Crafting Tech Teams last week.An offer to participate
I created a short, anonymous survey where I ask you to share your opinion about TDD in combination with your team environment. 👈 🙋🏼♂️🙋🏾♀️
I value your opinion. I was skeptical of TDD at first. I know most of you are too. I then tried it and the skepticism went away. It’s not that I have a ridiculous amount of irrefutable economic evidence by trying. But there was something about the experience that made adoption a no-brainer to me. My environment, my teams and my projects all seemed to lead me down this path.
I now use it nearly always when coding (which is rare, I have to admit) and mentor teams in doing so. But most importantly, I coach them through this level of skepticism.
I would like to learn more about this phenomenon as it seems to needlessly polarise and separate our development community. With your help, I might gain some new insights that I will be happy to share and discuss with you in the near future.
Encourage TDD, do not mandate—advice for managers
And the funny thing: they are both right. It depends. TDD isn’t something that intrinsically makes you more money. The contrary. The activity of writing test-first is a cash flow balancing activity that you perform to trade future risk for current time and focus. I believe this is the primary trade-off that non-practitioners object to.
But I could be wrong… What do you think? 💬
Trade-offs of this nature should always be made by the person who is most responsible for the outcome—positive or negative—and those who have the most tactical information at hand when making the decision. The engineering team. The pair. The ensemble.
Other decisions that fall into this category:
Merging strategies, continuous delivery
Feature flags
Refactoring
Cutting and negotiating scope
Creating knowledge with non-technical experts (DDD)
Documenting and gathering non-functional requirements
Planning delivery cycles and iterations (but not necessarily releases).
With astute confidence I can claim after observing hundreds of teams: Managers that get in the way of engineers having full autonomy on these decisions—including TDD—always create an environment of low performance, low standards and low psychological safety.
In essence: everyone is capped at junior-mid level. And sometimes this cap is self-imposed by the engineers themselves.
You can check out more about this in my second conversation with Jonathan Stark: “Building Autonomous Engineering Teams”.
The codebase rots even when frozen
Complex software projects have this interesting property that they are made up of smaller, fundamental pieces. Some of these can carry quite significant consequences:
A particular docker image
A set of frameworks and libraries
A security protocol that authenticates access
A connector library that allows you to communicate with a database or queue
Even in absence of any active changes to the codebase, the features within your project may unexpectedly stop working. Maybe the docker image had a breaking change. Or an external API. Or an NPM package was pulled.
So… every now and then you have to manually check if everything is still working. Everything for which you use a an external library or framework for. Everything that has access to 3rd-party services. Even the docker images, kubernetes config compatibility or network configuration.
How often? Ideally all the time. Daily. Weekly. Doing all of this manually incurs significant manual labor costs. It is teams that practice TDD’s who address this cost and save it from having to be spent by balancing that cash flow speculatively.
TDD is not the only option compared to non-TDD test after, test during or test later. However, it does provide a clear roadmap at the earliest point in time before the code becomes too messy to test: before it is written.
No emergent magic. TDD requires excellent design and product skills of the practitioners
This is where the tough choices come in. The median 50th percentile experience of software engineers in the world is less than 9 years. TDD is an easy practice. It takes no extra time on its own. However, it is the activities it encourages and enforces that take up most of the focus and energy:
Requirements gathering and business understanding. To write good tests, you need to test behavior. To test behavior you need to understand the product the software is serving and their users.
Tooling, OO, declarative or functional design skills. The first pass implementing will be efficient, but not very eloquent. There will be mistakes, but as long as they serve the tests you can quickly move forward. However, if you use frameworks that make design and isolation difficult, you need to rigorously learn the framework and how to test it in detail before making a productive step. There is a reason “react how to test <x>” is a top google search.
Excellent refactoring and code smell knowledge. Engineering teams that do not share deep understanding of code smells won’t know whether the code is better or worse after the refactoring step. This leads to nit picking, “I prefer X”, “At my old job…” types of conversation that are best set aside in a professional environment and kept to beer fridays.
TDD is a strong signal for lack of learning
Test-driven development is an agile, extreme programming practice. It’s simple. It’s lean. It promotes mistakes and being a don’t knower. A team crushed by autonomy and lack of psychological safety will be hard-pressed to deliver, deliver, deliver. Feature after feature. TDD is the last thing such a team wants. Adopting TDD would signal everything that’s wrong with such an environment:
Not asking “do we need this feature?”
Not asking “how valuable is it to the users?”
Not asking “where does the value come from?”
Not asking “how will we capture the value?”
Not asking “how will we roll this out to production?”
Not asking “how will we test this?”
Not asking “what should we test?”
Not asking “should we even test?”
Not asking “does this work?”
Not asking “should we re-implement this so it’s easier to add X?”
Not asking “how does the user expect to test this?”
Not asking “how much quality do we want to build in?“
Not asking “what would make this easy to test?”
Not asking “what tests do I need so this complex machine is easy to refactor?”
Not asking “how can I deliver something that works in 15 minutes?”
Not asking “how can I ensure small behavior changes don’t break existing behavior?”
Not asking “how can we deliver continuously?”
Not asking “how do we eliminate asynchronous code reviews and slow PRs?”
P.S.: We have a few more seats for the book club starting next week. Learn speed reading, blast through your procrastination and apply learnings from Implementing Lean Software Development. DM me LEAN to join.
I believe this mindset is the problem:
```
The activity of writing test-first is a cash flow balancing activity that you perform to trade future risk for current time and focus. I believe this is the primary trade-off that non-practitioners object to
```
To me, there is no trading off going on. When I write code, I will test the code. If I test it manually, then I will have to repeat the testing process every time I make changes. If I write code to test the code once, then the code will test the code every time. With TDD, I write the tests upfront. To me the actual benefit with TDD is not that it requires me to write tests. Writing tests is completely for the sake of writing code. TDD is about having clarity while writing code. It gives me the avenue to take moment & think about what I am going to do next, to define the parameters, to define the possible outputs, the implementation details. That is what TDD is fundamentally doing. If you are not following TDD, you will write the same tests for a given function, in which you will test all the control paths/inputs/outputs of the function. But, with TDD, instead of figuring out what needs to be done while writing the code, you think about the implementation upfront. Last bit: TDD helps me solve the problem before I began to code it.
I think that TDD is very underestimated. Is no silver bullet, as everything, but 90% of time is the best choice. And the main reason is the most underestimated skill you develop with TDD: baby steps. Become used to commit small pieces of advancement each minute or so totally changed my mindset. Benefits in productivity, but it becomes a new mindset - and it enables CI and TBD with more ease. So yes, TDD makes you more profitable for the company, in the end 🙂
And for those thinking “I can do baby steps without TDD” - good luck doing that without the TDD cycle supporting.