4

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?

  • related (possibly a duplicate): Is test coverage an adequate measure of code quality? – gnat Aug 12 '15 at 18:58
  • 2
    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? – Robert Harvey Aug 12 '15 at 19:09
7

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

[1]: assuming reasonably testable code

Especially when talking about error handlers, these have to be covered by tests. 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.

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) {
      happy_path;
    } else {
      error_c;
    }
  } else {
    error_b;
  }
} else {
  error_a;
}

can be rewritten as

if (!a) {
  error_a;
}

if (!b) {
  error_b;
}

if (!c) {
  error_c;
}

happy_path

These “guards” make the code more linear and in my experience both easier to understand and easier to test.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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