Why (not) TDD?

Shu

Late 2016 I joined a team that was, essentially, an XP team. Over the next 2 years we adapted and morphed our practices. We were very big on the experimentation. There were a couple practices that we held onto through all it all because of the value they added.

These ardently held practices were Pair/Mob Programming and Test Driven Development (TDD).

It was TDD that made this team the one I wanted to join. It made my code better. There's no question about that. I venture that if you've never done TDD and testing is ... not top of your list of things; You'll get better code by using TDD.

I advocate it for all the teams I'm coaching. Use TDD. It changes the code. It FORCES better design in. There are changes that are REQUIRED when you do TDD. You simply cannot (without intentionally forcing) get the same design as writing code first.

It's one of the first things I try to help teams to start practicing. I do this because if you don't understand WHAT TDD puts into your code; If you don't WHY those things get put into the code; If you don't understand HOW the code is different, then you don't understand high(er) quality code. If you can't understand that, we're gonna have very different learning opportunities.

I find that I've been advocating TDD as a learning mechanism. I don't know how else to help others learn what higher quality code 'feels' like. It is a stepping stone from what they THINK they need to do to what TDD forces the code to be. The forced code is higher quality.
A lot of the time I'm working with teams that struggle to create a test project, maybe that skews my approaches.
I find TDD amazingly useful for ensuring certain things in the code.

TDD gives code certain qualities. The simplest is testable. By definition, TDD'd code is testable. With the other standouts:

  • Testable
  • Decoupled
  • Inversion of Control
  • Tests of all behaviors

These are the biggies to me. I'm know there's some additional things that get into the code. I don't think it's TDD that drives them in. Some I can think of (extracting cohesion) are identified by the test suite. Duplication in test setup or tests themselves highlight issues. Using TDD does expose these, but the same test suite as test after could/would as well.

I've been using TDD since I learned it late 2016. It continues to be a great tool to produce higher quality code. Higher quality code than you can get without TDD.
...
Except when you can.

Ha

I want to be clear that TESTING is never in question. If you don't have a test for every behavior in your system, you are willfully creating and enabling a defective product.

This isn't an excuse to not test. Go f*ck yourself if you think it is.

TDD forces things into the code that require it to be higher quality code. It's not everything the code needs to be. I think TDD fails a lot because our industry lacks the knowledge of how to refactor. You can't correctly apply TDD if you don't know how to refactor. I don't have high expectations for most of the industry and their capability to refactor, especially safely.

Rewriting is not refactoring

Let's make sure we're clear that refactoring is the small changes, not a drop in replacement. This isn't a post for my rant about refactoring; but it plays into the overall picture.

I adopted TDD as soon as I understood it because it made my code better. I wanted to know WHY. I wanted to know WHAT changed. I wanted to see HOW it changed. The way I teach follows how I tend to learn... not 100% that's a good idea, but it is what I understand.

I practiced. I experimented. I studied. I researched. I had to understand why my code was better with TDD. I found understanding. I continued to experiment.
TDD forced my code to be better. I understood the difference. Could I get there differently?

I watched most of a video series with Martin Fowler, Kent Beck, and David Heinemeier Hansson (DHH). Links to those can be found here. I stronhly recommend watching them.
DHH has a post which spawned the recorded conversations. He probably says a lot of the same things I'm trying to say, but better.

I have hidden the fact I don't TDD. I don't mention it to the teams I coach. There's a little bit of guilt around that. I feel guilty with the "do as I say, not as I do" of this. And yet... I still do.
I'm going to shift that a bit; suggest TDD as a tool to help understand how to produce high quality code. If you struggle to produce code with TDD, it probably means TDD's a useful tool to learn.
TDD also forces high(er) quality code. You can't keep doing what you do and call it quality code if you struggle to produce code via TDD. I should restate that to, "if you struggle to produce testable code". I don't think DHH will struggle to produce code via TDD, but he mentions that TDD eventually becomes a blocker, a drag on his productivity. I understand that.

When I watched the "TDD is Dead" conversations, I felt I understood the situation as DHH did TDD, understood what it produced, and wrote code similar enough to that as to not need to write the test first. I don't think that's the case anymore. (I assume) He has found a way to write code that puts all the high quality attributes he finds value in, into the code without the need to use TDD as a tool.
TDD is a tool. We don't limit ourselves to just one tool. Sometimes we abandon a tool when we have something better.

I don't know what DHH does for his coding. I don't care. I'm not trying to be like him. He's just a well known figure that is vocal of his disapproval of how TDD is pushed.

I disagreed with him when I discovered TDD. I agree with him now. TDD is a tool, not a savior.

This post has been building for a while from my internal conflict between how I use and promote TDD. It's finally being written because of an exchange on twitter.

https://twitter.com/RonJeffries/status/1208748378068324365

TDD works best with very highly-factored designs, which provide lots of little things that can benefit from testing, and where TDD's forces suggest ways of doing that factoring.

That synergy, or its lack, may explain why some don't find TDD useful.

  • Ron Jeffries

His book "The Nature of Software Development" is one I highly recommend. He's great to talk with at conferences. He has said, and has a lot of great things to say. Not everything is great though.

The above quote was responded to by DHH https://twitter.com/dhh/status/1208793750635307008

This is the bullshit shame argument that finally got me on the barricades about TDD. That if you don’t find TDD useful, it’s actually because your code stinks. Fuck that noise.

I think there's a conflation of those who find no value in TDD and those who find value in TDD but don't use it for some reason. Ron's posts only talk about those not finding value. I find the whole thread an example of poor communication. Ideas not fully fleshed out. I hope that anyway.
If you don't find any value in TDD; I think maybe there are different struggles happening than the ones TDD helps you with. I think Ron's trying to say something useful... just not in a clear fashion.

There's value in TDD... I don't see it as the end state.

Ri

I have tools and techniques that have, largely, evolved out of understanding TDD gave me. They take me further. These practices utilize refactoring techniques that TDD can't force. High quality code requires techniques and skills that TDD doesn't provide. It asks you to do... things... Red-Green-REFACTOR... but it doesn't do anything to say WHAT to do in that refactor phase. Books have been written on it... But how do you know when to apply any given refactoring pattern?
Experience. The only thing that speeds that up is experience. TDD fails when you don't have that experience. How do you get that experience...
A smidge of a catch-22.

How do we break that? Teach the fundamentals of refactoring... Apply those. Write tests. MicroObjects is where I've landed on how to do this.

I think understanding and applying these rules are much more challenging than TDD. TDD is a stepping stone to understanding code smells. To understanding how/when to refactor. It's not an answer for refactoring itself. If we don't understand how to refactor... we can't effectively use TDD for the life of a product. It'll get too hard.

I think there's a process of producing quality code that follows the Shu-Ha-Ri. Application; Understanding; Evolved.
Fowler's description is great (except maybe some less gender specific terms).

Shu: In this beginning stage the student follows the teachings of one master precisely. He concentrates on how to do the task, without worrying too much about the underlying theory. If there are multiple variations on how to do the task, he concentrates on just the one way his master teaches him.
Ha: At this point the student begins to branch out. With the basic practices working he now starts to learn the underlying principles and theory behind the technique. He also starts learning from other masters and integrates that learning into his practice.
Ri: Now the student isn't learning from other people, but from his own practice. He creates his own approaches and adapts what he's learned to his own particular circumstances.

Am I so egotistical to think that's where I am? Duh. That's not why I look at my style of development as the Ri phase. I have, literally, developed my own approach and it changes for the situation.

I lead a team to deliver feature parity in 25% of the developer hours. We didn't use TDD. We used MicroObjects. Second project has still had zero bugs in the software without TDD. TDD taught me A LOT about code quality. I think it'll continue to be a great tool for that. It's a great tool as I'm exploring ideas or doing new things. It ensures what I have done continues to function. I use it as a tool though. A tool I understand exceptionally well... as well as when I don't need that tool.

I don't need TDD to produce high quality code. I can produce bug free software without it. Can you?

Use TDD

If you can't quickly and easily test all of your behaviors, you should use TDD.
If you can't get 100% code coverage, you should use TDD. (exceptions for logic-less 3rd party wrappers)
If your tests have giant Arrange/Setup sections, you should use TDD.

There's a lot of situations that TDD is going to help you. Not just to produce better code, but to understand why that code is better. If you understand that... If you can get the same quality into the code another way... Then TDD is a tool; don't make it your only tool.

Don't let the challenge of TDD or the ardentness of it's vocal practitioners keep you from learning to use TDD. Just understand there's no "one true way" to write software adopt that which works for you, and continue to learn new things.