3

I've been writing a lot of unit tests lately and have become somewhat obsessed with code coverage. However, I'm struggling justifying going for 100% code coverage, when so many tests would be a redundant, and really clutter up my unit tests.

For example, imagine an customer endpoint of an API. The user posts a customer ID, their location and then some data. Looks like /api/customers/:locationId/:customerId.

Here's an example of a code coverage snippet from instanbul, I'm using Node.js:

enter image description here

The first miss, failing to update the customer, is something that should definitely be tested. But the last 2, failing to find location and customer, are present on nearly every endpoint. Am I really to pass in an incorrect location, and then an incorrect customer, for every one of the endpoints (create, update, delete, etc.)? Or should I just live with those not being covered?

2
  • related (possibly a duplicate): Is test coverage an adequate measure of code quality?
    – gnat
    Commented Aug 12, 2015 at 18:58
  • 3
    Should "Could not find location" and "Could not find customer" (and the code that you haven't shown that is associated with those messages) be refactored into a separate method, so that you can just write tests for that single method? Commented Aug 12, 2015 at 19:09

1 Answer 1

11

Test coverage is good. 100% test coverage is absolutely reachable without insane amounts of effort1, except of course for those this-can-never-happen-but-lets-check-for-it-nevertheless assertions.2

1. Assuming reasonably testable code.
2. However, increasing test coverage has diminishing returns. Not all code is worth covering by explicit tests.

Especially when talking about error handlers, these have to be covered by tests.3 The “happy path” will be implicitly tested by normal usage. But if things go wrong – you want to make sure things still go your way. It would be a shame if a silly little mistake would mean your error handling code would itself produce an error. Proper errors also make it easier for users of the API to figure out why their code isn't working – they are an important feature of your code.

3. Testing such errors is most important when the errors are supposed to be recoverable, because those errors are part of your business logic and API. This is in contrast to assertions that directly terminate the process and don't allow for further control flow.

In the context of a web API, checking all input can be crucially important for security – defensive programming should be the rule, not an accidental feature. Obviously, this validation needs to be tested!

Another tip: In your example, your code has an extraordinary amount of indentation. This can be a sign that your function should be split up into multiple smaller functions that can be tested separately. Closures are also difficult to test; using named functions increases testability. Also, long cascades of the form

if (a) {
  if (b) {
    if (c) {
      return happy_path;
    } else {
      throw error_c;
    }
  } else {
    throw error_b;
  }
} else {
  throw error_a;
}

can be rewritten as

if (!a) {
  throw error_a;
}

if (!b) {
  throw error_b;
}

if (!c) {
  throw error_c;
}

return happy_path;

These “guards” make the code more linear and in my experience both easier to understand and easier to test. In that example, you just need four tests to get 100% statement and branch coverage.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.