DEV Community

Michael Nunez
Michael Nunez

Posted on • Edited on

Test Coverage - going from bad to good, and knowing when to do it

Having production code with good test coverage is very beneficial to a development team.
Broad coverage can catch bugs and unintended behavior from entering the codebase, and everyone can sleep better at night, but when a code base doesn't have much coverage (or any at all), it can be really difficult to introduce it.
We'll discuss when you should introduce or increase test coverage, and go in depth into the ways in which it's beneficial for you and your team.

Coverage for legacy codebases?

As a huge believer in tests, I make a concerted effort to add coverage to every project I work on.
Realistically though, there are times where tests are just not that beneficial.
For instance, when dealing with a legacy codebase (an internal API that is rarely updated), adding coverage offers diminished returns.
If your legacy codebase works reliably in production with little to no regression, adding tests retroactively may simply work to verify what is already validated in production.
Often, the legacy parts of an application serve as dependencies, so I suggest focusing your efforts toward testing the new API leveraging the old code.

Still unsure of the benefits to coverage?

Here are some benefits to increased test coverage:

  • Ensure your code works as expected
  • Deploy code with greater confidence
  • Decrease the likelihood of 1am phone calls

Ensure your code works as expected

Adding tests encourages you to conceptualize how you want your program to behave and account for edge cases.

Deploy code with greater confidence

The greater your coverage, the more confident you can be that new features did not introduce regressions.
In the event that deployment introduces a bug, tests can serve as documentation to both track down the bug and squash it.

Decrease the likelihood of 1am phone calls

Pager duty is not fun. Seriously.
Nor is it fun for the person calling you.
Increased coverage will help prevent bugs that have the habit of waking you up in the middle of the night.
Future you would appreciate that.

Okay, I'm sold! How do I start adding coverage?

Once your team is on board with increasing test coverage, I suggest first writing test for new feature work.
That way, new changes to the codebase are covered, and good habits will start to form amongst team members (and in turn, new hires).

Your next priority should be adding coverage for existing code (non-legacy), but this must be done much more carefully.
There are generally two ways to go about it:

  • Scrap the existing implementation, one discrete module at a time, and test drive the recreation of its code
  • Add tests to validate the existing code.

Scrap & test drive

Pros:

  • Scrapping and test driving will likely result in better design and code clarity.
  • The tests will serve as a form of documentation for your teammates and future self.

Cons:

  • Throwing out existing code can result in you missing a particular business case that wasn't clear within the implementation.

Add coverage to pre-existing code

Pros:

  • Generally a quicker process than scrapping and test driving
  • Coverage of an existing API can make refactoring easier

Cons:

  • May work to simply validate poor design.

Side effects

Testing certainly has a learning curve, so your team may spend up-front time figuring out how to best test new and old code.
This can result in a decrease in sprint velocity, and may require more complexity points in story estimation.
That said, adding tests to new features will reduce bugs, and in turn, decrease the cost of having to squash them later,
and adding coverage to existing code will pay down technical debt that can prove crippling if left unpaid.

Summary

To wrap up, increasing test coverage will improve design, increase team confidence and cohesion, and decrease the stress associated with regression.


Senior, Lead, or Principal developer in NYC? Stride is hiring! Want to level up your tech team? See how we do it! www.stridenyc.com

Top comments (4)

Collapse
 
tisek profile image
tisek⚓

Is it really possible to cover untested code?
And to cover years of code that is untested which also means it was not designed to be testable and therefore is tough to test?

From my experience, in the end we cover new features (that do not rely on too much old code) and easy to test code (helper classes or very small modules).

Covering the whole thing takes tremendous time not only of test writing but also massive refactoring to get it to be testable. Problem is, we do not have tests to cover our asses while refactoring so the risk is really high.

Of course keeping untested old code is risky too...

At the end of the day, I believe that being in the situation of a big codebase with very little testing is awful with very little hope of improvement (does not mean we should not try)...

Collapse
 
_bigblind profile image
Frederik 👨‍💻➡️🌐 Creemers

s it really possible to cover untested code?
And to cover years of code that is untested which also means it was not designed to be testable and therefore is tough to test?

I'd go with the Scouts mentality of leaving code better than you found it. Add tests when you're touching a piece of code, so you can already edit it to be more testable. Yes that might be a slightly risky refactor because it's not covered by tests, but then again, it's no more risky than the development you did before TDD.

Collapse
 
hjortholm profile image
Kim Hjortholm

+1 for starting with test coverage for new features
For legacy code, one option might be partial/incremental refactoring of code and apply test coverage for "low hanging code fruit" e.g. legacy features easy to recode/restructure

Collapse
 
tstephansen profile image
Tim Stephansen

I'm a strong believer in unit tests. I heard a while back when I first started programming that if it's hard to write a test for something then you should probably look at rewriting that section of code. Obviously this doesn't apply as much to old code as it does new features depending on the application (if it isn't broke don't fix it). This "rule" does make me look at the code I write with more scrutiny and it helps me to write more maintainable code. Another added bonus of unit tests is knowing that what you wrote doesn't introduce any unexpected problems (if you write something and unit tests that were previously passing are now failing then it allows you to catch the problem before pushing).