Code coverage should not be the goal

2 min read

Let's look at a small code sample.

This project contains a single utility function:

const toUpper = (str) => str.toUpperCase()

module.exports = toUpper

Simple enough. We take in a string, call its toUpperCase method and return the result.

Testing this should be pretty straightforward. We'll use Jest for this example:

const toUpper = require('../index')

describe('Test the one function', () => {
  it('Works with a string', () => {
    const expected = 'TEST'
    const result = toUpper('test')
    expect(result).toBe(expected)
  })
})

Great! We have a test. And even better... we have 100% coverage on our code!

100% Code Coverage - woohoo!

Now we can move forward with full confidence in our code.

What could possibly go wrong?

As it turns out, 100% coverage just means that our test runs all of the code. That doesn't mean we've tested thoroughly or that we have all the right tests in place.

If we were to pass null, undefined, an Array or an object literal into this function, each of those would cause a runtime exception.

Now, you might argue that type checking like TypeScript or flow would solve those problems, and I wouldn't disagree with you on that. Not every projects has types. Without types, a developer or team who is focused on the metric of code coverage could be lulled into a false sense of security with that 100% coverage number.

The point here isn't that you need 100% coverage, or that every piece of code needs to be tested for type-safety just because you're not using a typed language. The point is that 100% coverage shouldn't be the primary objective for your tests. The point is, that you should be focusing on testing the right things. That means writing quality tests for the things that are likely to break. The critical logic, the complex parts of the code.

I personally write tests more for the purpose of driving my API design than for safety, but safety is a nice side-effect of good tests. The tests that I care about once the API has been designed, are the ones that will help me maintain the code long-term. The tests that help document the complicated bits of code and that will catch my mistakes when I end up having to make changes down the road.