TDD is widely respected, yet I wonder why it is not that widespread? I did a number of TDD workshops and was always surprised how few people have actually tried it and even fewer – used it on production. Talking about experienced developers, not just juniors! Many influential developers use and promote it and I admit that this was big part of the reason why I started to use it. Yet it seems “All the cool guys use it, so sure it must be great?” is not good enough reason for people to adopt the practice of writing a test first. Developers ask “Is it worth it?” and want to know why.
For a while I could not produce a satisfying answer. This post is my latest attempt to do so.
What does Test-First mean?
There are plenty of “Driven-Development” practices out these – TDD, BDD, ATDD… They are all similar and a bit different. That could be a topic of another post, but if I had to sum it up in one sentence it will be this:
Write a test first, before your “production” code.
Is it worth it?
I feel I gained a lot from the practice, but how much and why? I am using it for a while and the developer I am now is different from the developer I was when I started years ago. Also adoption was a gradual process, so it was a bit blurry what was gained and when. It is not easy to compare and I wanted to measure it.
How do you measure something like this?
You stop doing it and compare results.
So I stopped.
Well, not because I really wanted to 🙂 It kind of happened. Here is the story I’d like to share with you.
Few months ago I moved to a new job. There are plenty of new projects I have to tackle now and big part of the time I spent so far was on our website back-end. The catch here is the technologies and tools on that project are quite different from what I knew and was used to when I started.
I am and have been Java Developer for 10+ years now. Website back-end is written in Coffescript, using Sails.js MVC framework, sitting on top of Node.js. Instead of using IntelliJ IDEA I had to type code in Sublime Text.
Put on top of that the new business domain I had to learn and the project tests which were not well maintained and had to be cleaned before they were useful.
So here I am – eager to produce something, but with so much to learn and without working test suite. So I did not write any tests and relied on the browser and curl to hit my code and check the results.
Everything was new, so I worked in small increments, one little step at a time. I also had to debug a lot. As you can imagine, progress was slow. Website was working, but the code base was not in the best shape (remember the state of the tests?). As a result, everything felt and frequently was more complex than initially expected. Simple things took some extra time, I am sure you all know what I mean…
At last I finished first few small features and presented my work for review. Since I was new to the tools and technologies, naturally there were issues with my code I had to fix. But refactoring was slow, because I had to test the same thing manually, again.
So it was time to set up tests to be run. And not just for me, project needed some cleaning in general. The plan one of my new colleagues came with was to ignore all previous tests and start writing them in a new folder. He then configured our build to run those by default, until the old ones are cleaned. Of course we wanted the old ones fixed. I did not had the understanding of the codebase and technologies at the moment, so this was the next best thing. With deadline approaching nobody had a lot of time. But we did not abandon it and set it as an ongoing project instead. We wanted to improve it, one step at a time.
I knew I needed tests, but were not sure how to write them at first. Luckily testing with Mocha was easy and I started to pick it up. I noticed I was spending less time debugging, but still felt too much, compared to my Java code. Of course I also know Java much better, but this was not the only reason. When I was writing tests after implementation, frequently my code was not passing the test so I had to find why. So I had to debug. On the plus side I could refactor quickly now.
Around that time I configured Sublime to run my tests inside it. I stopped hitting the command line to run tests and stayed in the editor. That also increased my productivity a bit. This exposed another issue
My tests were not focused and small enough.
I realized I was using the terminal and debugging more because the steps I was taking while coding were too big. I would hit a stack trace that would be too long for the small window Sublime would open for me when tests are executed. Issue was not with Sublime, it was a symptom I was doing something wrong. So I started not only to write tests first, but also to always start with the smallest increment possible. Usually that will be negative test cases.
At that moment I noticed something funny. I would write my Java code test first almost exclusively. Yet this was not true for my JS code, even after learning how to write tests for it. There was still something missing. I had issues because of that.
I was not always covering all test cases. Main test case I always covered, but sometimes would not bother with all the negative test paths or will miss ones. Those were the tests I usually had to write after implementation. It was slowing me down so I did not put that much effort in it. I was not motivated to write tests for code I have already written.
But the bigger issue was something else – when writing tests after code, I was testing just my implementation. As a result I had a few bugs when my code was working as expected, but I had not understood the requirement correctly. I did not think well enough what my specification was beforehand.
Solution was simple – write all tests in advance and use them as a specification. By that time I was much more comfortable with my tools and language, making faster progress and devoting less time for learning. I found a step that was comfortable for me. Time in debug mode went down a lot. I started to refactor my tests as well. Test coverage was finally good and that enabled changing some of the old code. In the meantime we finally cleaned older tests and static analysis tool was added to our builds.
All that happened in a few weeks, so was a fast forward of how I learned TDD using Java years ago.
It also made me think what writing a test first helps me do:
- Isolate the problem – you focus on this small part of code only. No more switching to the browser or checking the DB to verify things are working
- Less debugging – when you know your code is broken in one small method, it is much easier to spot the issue
- Breaks problem in smaller tasks – your subtasks are your test cases now
- Work in small increment – leading to less errors
- Think about what your code should do before you write it – test your understanding first, your code second
- Know when you are done – all tests are passing and you cannot think about new one
- Automated proof your code works – enabling refactoring, leading to better code and happy developers
Forget this post said anything about testing and look at the above list.
All this is something important you should be doing anyway.
Code is and should be written like that, even if you do not write test first.
And if this is so important, why not just use a process that helps with all this? Like writing test first? ?