Unit Tests Slow Me Down

Gregory Leman
CodeX
Published in
6 min readMay 6, 2022
Photo by Emil Bruckner on Unsplash

It only makes sense. If I’m forced to write unit tests, I’m writing more than twice as much code as I would if I just wrote what I wanted to begin with. When I make changes to the code, I’m forced to also change the unit tests. A lot of my code just isn’t testable. Sooner or later, they become stale and I start ignoring the tests. Therefore, they were a huge waste of time. I’ve got tight deadlines because management is pressuring me to go faster, so avoiding unit tests is an easy choice. And don’t get me started on how stupid TDD is.

Those of you who have read my other articles realize that’s not what I think. But unfortunately, that’s an opinion that is held by quite a few people in our industry. They really do believe that unit testing is unnecessary and just slows down the process.

The more thoughtful people that hold that position rely on the idea that higher levels of testing subsume what the unit tests cover. There’s no reason to write unit tests as long as we’re doing integration, system, acceptance, performance, and security testing. We’re going to get the same quality at the end of the process if we do all these other things, so why waste the time of writing unit tests?

They are completely missing the point. We write unit tests in order to go faster.

Building Blocks

Software development is like building with blocks. We take a large, complex problem and break it into manageable pieces. We then build those pieces and assemble them into even bigger pieces. Eventually we’ve assembled a large number of pieces into a working system.

You can certainly assemble all of those pieces and then test the resulting system. But this runs across the classic debugging problem: “I know something is broken, but I don’t know where.” Debugging is the process of diving into that huge pile of blocks and figuring out which one is defective. Sometime it’s a combination of different defective blocks working together that create a defect.

The Code-Debug Cycle

There’s a saying in software development that the first 90% of a project takes just as long as the last 10%. That’s because when you’re debugging you don’t have much of a handle on how much work is left. There are lots of heuristics around tracking bug counts, time to resolution, etc to try to predict when a project will get to a stable release, but these are all just guesses. Sometimes fixing that last bug can cascade into breaking other things. Sometimes those regressions are happening behind the scenes so you don’t know about them until the next user acceptance cycle.

Here’s what a typical software development project looks like that doesn’t waste time on unit testing:

10% is requirements/design, 30% is coding, 60% is a test/debug loop. Most software development improvements are focused on trying to cut down that 30% of the time spent coding. That’s the logic that says if we eliminate the time spent unit testing we’ll get the code done faster.

Doesn’t it make more sense to do whatever we can to take a chunk out of the test/debug cycle? The payoff from a savings there is 2x that of slightly faster coding.

Back to the blocks analogy. Debugging gets very hard when you don’t know which (or if any) of your blocks work correctly. You’re literally working on a fragile foundation. Any of those blocks could crumble and break other things. Conversely, it’s much easier if we know that each of the individual components all work as expected and we’re just tracking down problems with the interactions.

Let’s say it takes us 50% more time to write unit tests as we are coding. (I don’t agree with that estimate, I think it is actually faster to work using TDD than not.) Let’s assume a project of 1,000 hours. Instead of 300 hours of coding we’ll instead do 450 hours of coding. Our test/debug time only has to be cut by 25% to make up for the extra time writing tests.

The savings are a lot more than 25%. By now having solid blocks we can:

  • We’ll never have a regression. The same bug won’t appear again because it would be caught by a previous test. For every bug we do find, we create a test that verifies it doesn’t return.
  • We won’t have any bugs where the behavior of a block does not meet the requirement. We have tests for the blocks so we go in knowing they work as specified.
  • We don’t have to worry about defect cascades. If we make a change in one place we can run the entire set of tests and know whether we broke something somewhere else.
  • We don’t have to be afraid to clean the code. We can move things around without fear. We can simplify a block and guarantee that the behavior has not changed.
  • Actual nitty gritty debugging is faster when you’re doing it via unit test rather than stepping through a debugger. Most debugging time is wasted just manually setting up the conditions for the tests. Most bugs that you find are really just tests that were left out.

In my experience, starting with really a solid unit test suite eliminates 75% of the time spent debugging. Our 1,000-hour project just went to 450 hours of coding and 150 hours of debugging. The entire project took 40% less time. And the quality is much higher than an ad hoc approach.

That 450 hours for coding is high, especially if you are following TDD. It’s actually just as fast to code with TDD as without. Ever have brain freeze trying to solve a coding problem? That rarely happens when you’re just trying to write the next failing test, or writing just enough code to pass the test. Every time you run the tests and get the green light your brain gets a hit of dopamine. This helps you get into flow and stay there. The real answer is the overall savings are closer to 50–60%.

When I Make Changes to Code I’m Forced to Also Change the Unit Tests

You’re not testing correctly. A unit test should test for a behavior. It should not be coupled to the code. The only time you need to change the tests should be if you change the expected behavior or are adding additional expected behaviors.

A Lot of My Code Just Isn’t Testable

That can’t happen if you’re writing your tests first. One of the primary benefits of a test-first approach is that it forces you to design your code in a way that is testable. You will also find that code that is designed to be testable is a lot less fragile.

One caveat I would make to this is that it’s difficult to test UI code. You can test that a UI has a set of elements that you expect, but when you’re move widgets around by a few pixels or changing the color to just the right hue you’re not going to be building unit tests.

The Tests Become Stale

Not if you’re testing behaviors. The tests are actually a great way of documenting expected behaviors. The tests can’t get stale if you are updating them as the expected behaviors change.

Management is Pressuring me to go Faster

I doubt it. Management is pressuring you to release faster. They want you to be more productive. They couldn’t care less about how fast you are typing or how much code you write, they want to see results. Taking a little bit longer in coding will make you look like a hero when it flies through acceptance testing.

If You Want to Go Fast, Go Slow

As military special operators say, “Slow is smooth, smooth is fast.”

As title of this article says, unit tests slow me down. And that’s what makes me go faster.

--

--