Introduction
In the first part of this series I want to share my personal experience with test-driven development(TDD), how I got there, and how it makes the development process much simpler with an end result of higher quality code.
Bad Tests ๐
I once worked on a project using a micro-services architecture that had a contractual obligation for 80% code coverage. Before this most of us had not written unit tests, let alone practiced TDD.
In the beginning, I developed the way I normally did at the time, that is slam out a bunch of code, run the application, wonder why it wasn't working, run the debugger, fix the bug, rinse and repeat(also known as bug driven development).
I would write the unit tests after I finished the code. Sometimes I would even create another task to do this work under after I had already committed the production code(no bueno). It was deeply frustrating and oftentimes took longer to write the tests than it did the feature (the tests were also garbage).
Starting to Write Better Tests ๐ฑ
As the system grew and more services were added, it became increasingly more difficult to develop this way. Running the entire system in a local environment was not always feasible and the feedback loop was far too long to do anything efficiently.
As my frustration grew I started looking at how to write better unit tests and how to write testable code. I read Clean Code which is closely related but not quite directed at TDD, discovered the SOLID design principles, and absorbed as much as I could from the internet.
So I started writing a little bit of code and then I would write the unit tests at the same time. This kept my code clean, and I could develop entire features without ever firing up the application. The tests were all of the feedback I needed and then I could verify that the system was running as expected less frequently. The issues that did occur where usually plumbing related or not understanding the problem correctly.
Going full TDD ๐ก
I then came across this talk by Elliotte Rusty Harold, and one of the audience members basically described how I developed and asked why he should write his tests first. Rusty's response was to the effect of 'so you are writing a little code, writing a little test, writing a little code, etc... I'm just asking that you cut out the first step and write a little test, write a little code.'
A light bulb went off and that's what I started doing. Before I wrote any code, I wrote a test first. Suddenly writing complex features became not only easy but fun! I knew what the system had to do, so I would write down a list of tests, and one by one I would write the test, then write the code that made it pass. This let me build up the feature in tiny baby steps and I got immediate feedback, kept the code simple and clean, and gave me confidence in the code that I had not experienced in any other way.
Conclusion ๐
There are many other benefits to TDD documented elsewhere, but I wanted to explain my evolution of how I got there and how it affected me practically.
I would say that writing the tests as you write the code gives you about 80% of the benefits but going full TDD will take all unneeded complexity out of the design and leaves you with higher quality than you might have had otherwise.
The bottom line is, is that it makes coding easier and with the added bonus that your code will be by default high quality.
Throughout the rest of this series, I'll be going over the basic anatomy of tests, test doubles (mocks), how to write tests for legacy code, and how to practice TDD.
I hope you'll join me!
P.S. If you are wondering why there is a surgeon washing his hands as the cover image. Robert C. Martin(aka Uncle Bob) describes the technical discipline of TDD as akin to the ritual of a surgeon meticulously washing his/her hands or double-entry bookkeeping in accounting. Sometimes seemingly arbitrary but highly effective.
Top comments (11)
Looking forward to the rest of the series. I wonder if you plan on targeting higher level tests. I mean, unit tests are mostly easy. But how do you test request-to-response with all the middleware, controllers, database and microservices. That has always kept me wondering. How people efficiently do that.
Thanks for the feedback! That's a super interesting topic but maybe beyond the scope of this series. I do plan to talk about acceptance testing when covering outside-in TDD but that's about as high level as I plan to go.
There are a lot of interesting strategies for example something like chaos monkey which turns off random containers to flush out faults.
I will keep this in mind and can maybe do a follow up series!
Hmm... I see so many tutorials on how to write good tests, but I've never seen one on how to get started with writing tests...
Thanks for the feedback! I can address that in this series. Is there anything specific you'd like me to cover?
One example I can think of is writing tests for a "network" class that sends and receives data.
you got it!
I tried TDD 5 years ago. I cannot stress enough how powerful it is! It allow me to write code faster and with less bugs. Now whenever I start with a new technology, I first check how to write unit tests :)
Agree ๐ฏ. It's hard to go back once you get the hang of it!
Tdd can be interesting as an exercise in discipline as long as you mean unit tests.
Yes definitely! Higher-level tests are important as well, but that is out of the scope of this series. Albeit acceptance tests that use mostly real objects are an important part of outside-in TDD.
Love to continue the discussion here! Let me know if you agree/disagree or have any questions.