TDD is Not for Me

Jon Calhoun on March 15, 2019

This article was original posted on calhoun.io, where I write about Go, web dev, testing, and more. This will be the second time I've written ... [Read Full]
markdown guide
 

Thank you! I'm SO glad to see others say that TDD isn't for them. Unit testing was hammered into me throughout university and I always felt like less of a developer for not being able to follow it. Now that I'm reading articles like yours, I feel a lot better knowing that it doesn't have to work for everyone (and that I'm not necessarily a bad dev).

 

Fair enough, as long as you end up at the same spot- resonably bullet-proof code that’s easily debug-able.

I have spent the majority of my career as a “code janitor“ - retroactively adding tests to legacy code that was not built with TDD in mind. In my experience, this is an expensive, slow, short-sighted way to develop. If RGR isn’t for you, that’s totally fine! However, I do agree with Uncle Bob that your tests should cover happy/sad paths.

As the Ba’hai would say - “many lanterns - one light”. As long as we’re all moving towards the same light, I say use the lantern you’re with which you’re comfortable.

 

Does this apply to both backend and front-end development in your experience?

 

I would say it does, yes, if for no better reason then it provides documentation for the code which you have written.

That said, I agree with John’s sentiment that TDD can be sometimes be done simply to “make the numbers”. Tests should serve the code base and the developers, not the other way around.

 

I have a quite good experience with TDD. Actually it helped me save tons of time. I love it mostly when I do fixes (I know exactly what should work, so it is easier to begin with tests, unit or feature).

However, I prefer API first for my new projects: I know I want to use my tool in a certain manner, with a specific syntax, chaining my method in a particular way. So I begin to write my methods, then I comment with the algorithm, and I go deeper and deeper until I need elementary methods (like checking if a key exists,...). Once I reach this point, I start unit testing those methods, and I continue...

So I agree with you, TDD is not the answer, and we teach it in a way that it prevents thinking the big picture.

 

However, I prefer API first for my new projects

Same.

 

For me, testing is about codifying a behaviour. Sorting is a great example: does my sorting function sort this list of numbers the way I expect? Nice black box testing would not care about how that algorithm is implemented; we only care about behaviours. Does my sorting function sort quickly for large sets of numbers? Does it perform well for a variety of distributions? What about degenerate cases. I suppose we could imagine a series of tests that would force us to write quicksort rather than mergesort or shellsort but... meh.

By using TDD the developer derived an algorithm that was incredibly slow

I've definitely seen that too. I mean, what can I say? TDD will not make you smart. TDD is not a panacea that can replace an understanding of algorithmic complexity. You can stick a test in that requires you to make the sort happen faster than some number, but there is no way TDD is magically going to show you how to make it pass. TDD is a tool to help you think, it's not a substitute for thinking.

  • Write a few test cases demonstrating the basic functionality I expect.
  • Spend time to think about how I might achieve that functionality.
  • Implement a rough version that gets my tests passing.
  • Refactor as necessary.

Are you writing a rough version that makes all the tests pass the first time (which I don't think I could do), or are you iterating by making the tests pass one after another with a refactor after each? In either case, sounds enough like TDD to me - testing first, using tests to think about code.

 

I think TDD is a really good practice for beginners because 1) It gives you a better understanding of what to test and how to write those tests and 2) It forces you to learn how to break down large complex problems into smaller more manageable "blocks" of logic. #2 is more important.

Once you get good at those two things, I think TDD is just another tool in your toolbox, sometimes it makes sense, sometimes it doesn't. Sometimes TDD helps a lot by forcing you to write clean code and focus on one small problem at a time, and sometimes it's better to just get lost in the code for an hour or two to solve a problem without worrying about writing tests every five minutes.

Personally my goto code "process" looks like this:

  1. Start with BDD (which is basically TDD but instead of writing unit tests you write feature tests)
  2. Allow myself to get lost in writing the code for an hour or so till I pass the feature tests (usually the code is pretty sloppy and contains several "megafunctions" at this point)
  3. Write unit tests for the code I just wrote and use those unit tests to refactor the code into smaller more manageable functions (or "units".)
  4. Document the code, add comments, and focus on renaming functions and variables to make the code as readable as possible.

I find this process helps me a lot by allowing me to focus on different activities (which usually require a completely different mindset) for larger chunks of time, rather than shifting constantly between testing and programming. Instead I start by defining the scope of the problem I'm trying to solve (feature tests), then focus on programming and problem solving to pass the feature tests, then shift gears to unit testing and refactoring the code to make it cleaner, and finally focus on the documentation and readability of all the code I wrote. Then I write new feature tests for a different problem scope and rinse and repeat.

 

Off-topic:

This article was original posted on calhoun.io, where I write about Go, web dev, testing, and more.

I've come across the site before and was really impressed by it. It goes deeper into the real-world issues that simply hello-worlding all the time. :-)

 

I also dislike TDD in many scenarios. I've seen the same problems you've described. There are cases where I use it, but more often, I write the code, write the tests to cover the code's features, and work from there.

 

I have found it extremely useful during software maintenance and evolution. I put an example:

I had to improve one part of one complex report engine, in order to support some formatting. The component I did need to improve had interactions with not so many different components, just with the one who use it. The problem is that there were too many possible inputs and I could not analyze the whole engine to understand who it works.

They had a big set of business tests, that tested the possible workflows of the whole system using BDD tests. Hundreds of reports were tested for every known use case (crm, purchase, sales, inventory, production, etc..)

What I have done is to run the complete set of tests with a modified version of the original component that did save the method calls and the responses.

Then I had hundred of unit tests for my new component, that I did run on every change. I did safe even resources and time, because now I didn't have to run the whole system in order to test my component.

After few days my replacement component was ready, with all the known cases covered, and the new ones required.

The replacement was successful, IMHO.

And I did save time to know how the whole report engine work.

That did demonstrate me the useful the workflow (bdd) tests were to generate unit test contexts.

 

TDD is about building thing that do something! It isn't just documenting your code. It should provide some useful feature. Like preventing errors where several people have their hands in the code. Or even provide a pop-up, UI tutorial on how the API under your application works. And possibly even generate some type of state (a database, config, or .json files) that customizes how your application will work. Otherwise, it is just boiler plate code no one will read.

I also hate the unit-test-only attitude some have. To test your application for reasons of code coverage alone is stupid. Same as: look how many lines of code I wrote today!

 

TDD is yet another buzzword in the industry yet it ultimately leads to increased development time due to its required processes. I'm glad I've never been on a project that requires it and I believe traditional unit tests written after the solution is created is the way to go.

 

What turned me off TDD was how every introduction I read always started with something like "what happens if we pass a string when we're expecting a number" - I'm using a language with a static type system, that isn't going to happen (well I guess someone could do something perverse with reflection, but that seems like a bit of a stretch). Then there would be "what happens if there's an unexpected null" - I'm using static analysis tools to stop that happening in the first place.

Then once you got past the tests that are better left to better tooling, most of the tests I was being encouraged to write seemed to be doing little other than testing the JVM (which I think is a side effect of "only writing enough code to pass the test").

 

I am a bigger fan of fault injection testing. It is a lot more complex, but the payoff is way bigger when you learn to develop reliable and redundant infrastructure that responds predictably to system faults.

 

I think it depends on the state of the project whether TDD is the right approach. If you build a prototype PoC from scratch and are alone and have unclear and "agile" specifications, I think TDD slows you down.

But as soon as you have other developers on the team, or work on a project that has matured over time, then TDD helps you catch errors early and avoid massive communication overhead. I see tests often as a communication tool, it's the best way of telling another developer what I intended with this piece of code and that he should not change this behavior lightly.

The problem with TDD and PoCs or prototypes is that is unclear what the program should or shouldn't do in the beginning, you just get a rough idea of the problem you should solve and then explore a solution that might not be right and change rapidly. I do like integration tests on the API level in this state to make sure I don't break something that is already agreed upon, but have the freedom to refactor the whole application underneath without having to adjust too many tests.

I'm not saying you shouldn't write any unit tests when starting a project. When you write code that you know will stay, insure high test coverage. Otherwise, the developers that come after you will have a hard time not breaking stuff and won't feel confident in further developing what you started.

I believe there is just not easy rule on when to write tests or practice TDD it totally depends on the project you are working on.

 

Very interesting opinions on TDD. Do they change if the project is collaborative? Regardless of possible complications, wouldn't it be better to always push for TDD in that scenario?

 

Requiring tests is very different from requiring TDD. I think collaborative projects can benefit from tests, but I don't think TDD is a necessity at all for that.

Put another way, whenever your teammates check in new code you see both the complete code and the tests all checked in together. Does it really matter to you whether they wrote the tests or the production code first if the overall quality is the same either way?

 

I see your point. So, there's a difference between doing general tests and TDD? Would writing general tests use TDD frameworks like RSPEC you just wouldn't follow TDD paradigms, or would it be something different?

TDD confuses a lot of people because it is often used interchangeably where it shouldn't be. In reality, Test Driven Development is a development process where you write tests BEFORE production code, and there are a few other caveats.

rspec and other tools aren't TDD specific. You can write tests in rspec after writing production code just the same. I used to do it all the time when building ruby apps.

My bigger point here is that you should use TDD when it benefits you, not all the time just because someone told you that you should.

 

I agree in some way with this. I've seen teams trying to enforce it for everyone, and I think it's a mistake, I felt bad for having a hard time learning it.

More specifically, for front end developers, when I am building a new component/page, I prefer to write my HTML first, make it semantic, accessible, style it, and then add logic needed for the interactivity. I find TDD to not always work in that case, as I find myself focusing more on implementation details than the actual feature I am trying to implement.

But when rewriting a whole page in another system though, I find it really useful as I have a pretty good idea already on how it's going to look like and can make sure I cover all the cases the code being rewritten was doing.

I think in the end, I do my own version of TDD rather than following it to the letter. As long I am still confident my code is tested, I think this is fine, and this is what I try to show more junior co-workers as well, so they find what works for them in a more natural way.

 

There's a hilarious and insightful video from Gophercon Singapore with the same contrarian attitude.

 
 
 

Gophercises - Programming Exercises for Budding Gophers

that link is down for me

 
code of conduct - report abuse