Some developers advocate doing test-driven development 100% of the time. Other developers think TDD is for the birds and don’t do it at all. Still other developers go in the middle and practice TDD more than 0% of the time but less than 100% of the time.
I personally am in the camp of practicing TDD some of the time but not all. Here’s my reasoning.
When TDD makes sense to me
It’s not the case that I use TDD, or even write tests at all, for every single project I work on. But I do pretty much always program in feedback loops.
The “feedback loop method” is where work as follows. First, I think of a tiny goal that I want to accomplish (e.g. make “hello world” appear on the screen). Then I decide on a manual test I can perform in order to see if that goal is accomplished (e.g. refresh the page and observe). Then I perform the test, write some code to try to make the test pass, perform the test again, and repeat the process with a new goal.
TDD == automated feedback loops
The way I view TDD is that it’s just the automated version of the manual work I was going to do anyway. Instead of making a to-do note that says “make ‘hello world’ appear on the screen” and then manually refreshing the page to see if it’s there, I write a test that expects “hello world” to appear on the screen. All the other steps are the exact same.
I’ve found that TDD works great for me when I’m working on what you might call “crisply-defined” work. In other words, the requirements I’m working to fulfill are known and specified. I find that I’ve found that there are other scenarios where TDD doesn’t work so great for me.
When TDD doesn’t make sense to me
Coding as production vs. coding as thinking
It’s easy to think that the reason to write code is to create a work product. But that’s certainly not the only reason to write code. Code isn’t just a medium for producing a product. It’s also a medium for thinking.
This is the idea behind a “spike” in Agile programming. When you’re doing a spike, you have no necessary intention to actually keep any of the code you’re writing. You’re just exploring. You’re seeing what it looks like when you do this or how it feels when you do that.
You can think of coding kind of like playing a piano. Sometimes you have some pieces of music already in your head and you’re trying to record an album. Other times you’re just messing around to see you can come up with any music worth recording. These are two very different modes of engaging with your instrument. Both are very necessary in order to ultimately record some music.
TDD doesn’t mix great with spikes
I often find that a spike phase is necessary when I’m coding, for example, a feature with known big-picture requirements but unknown UI specifics. In that case my test would be so full of guesses and placeholders that it would be kind of a joke of a test, and it wouldn’t help me much. In these cases I give myself permission to forego the testing during the spike period. I come back after I have some working code and backfill the tests.
- I don’t practice TDD 100% of the time. (I believe I do practice TDD the vast majority of the time though.)
- I view TDD as the automated version of the coding workflow that I already use anyway.
- Producing a work product is not the only reason to write code. Code can also be a medium for thinking.
- When I’m in the mode of using coding as a way to think, I find that the benefits of TDD don’t really apply.
Thank you very much for your article an analisys.
I didnt Know and couldnt understand my mind vs my feelings when i were coding.
Your article makes me to see crear.clouds are gone away an let me apreciate the beauty of blue sky again.
Im confident.. im in the rigth tdd way …. now
I Know.. i wrote a little friky style… but im sure you are reading until here.
Thanks! Glad it helped.
Hello Jason. Thank you for this article, it’s really interesting. I’m going to feature it on the newsletter I’m currently maintaining if you want to take a look:
I totally agree. I even have an open-source project that was written without TDD and has won a Fukuoka Ruby Award Competition 2022 Special Award, judged directly by Matz, the creator of Ruby.
The project was one big prototype of whether I could build an object-oriented hierarchical DSL for the Ruby bindings of the LibUI GUI toolkit (which used to advertise the need for someone to write such a wrapper on top of it since it is imperative and has a C-style API):
Do not let anyone deceive you into thining TDD is truly needed 100% of the time. Those people do not even have an answer when you point out to them “Steve Jobs didn’t have his engineers write code with TDD and his company’s code is better than the code of 99.99% of the software industry”. Some of those people would rather just lie to themselves and believe the lie to cover up their lack of skill in software engineering, which should not require TDD. I mean Rails is another example that is one of the most successful in the world and was not written with TDD. So, avoid people who do not listen to reason because they are trying to sell mediocre “Agile” consulting services.
Cheers and thanks!
I think that it is easy for new developers to fall into the extreme camp of 0% or 100% (or at least trying for the 100%) this article is a nice way to explain that there are middle grounds.
For me TDD is 0% of my workflow. By that I mean that I do not spend time writing a test suite to be run repeatedly throughout the life cycle of my project before I develop the project. A test suite does not inform my architecture or the what or how I implement an application. If I need to figure out a how I generally turn to a real-time session in irb, pry or the rails console.
On the face of it that statement may lead you to believe that I do not write automated tests. Not true. I do. I write regression tests at the unit level and at the system level. I write them after I create the main code.
Regression tests tell me what is supposed to happen. They also demonstrate the correct way to call a method on an object pr invoke a service – as an example. When a regression test fails, it means that someone changed the code in a way that breaks the “specification.” Now they have to determine if that failure in the regression test is an indicator that they added a defect or changed the specification for that specific process. In either case, code must be fixed. Either the main code or its regression test.
The majority of the regression tests that I write in the beginning of a project are unit tests. As the project matures, more system-level tests are created. I have often called this approach the two-humped camel approach to test emphasis.
Consider a bell-curve with the y axis showing importance and the x-axis showing time. As we move forward in time we spend more effort on developing unit tests. Then the effort falls off because all the units are completed. The next curve is for system-level tests. The two curves will overlap a bit in time. We never write a system-level test before the unit tests. At some point we start…. and there is an overlap where we are writing both unit and system-levels test.
I’ve called this approach to testing the two-humped camel approach in which unit testing is most important in the early days of a project. As the project matures, system-level tests become more important.
If I were a visual artist I draw a picture… hopefully my 1000 words are sufficient to represent the camel. 🙂
Just wonder when will Uncle Bob and Kent Beck enter the chat. LOL!