Skip to main content
I've become less absolutist over time. Added some comments.
Source Link
amon
  • 135.2k
  • 27
  • 292
  • 384

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

[1]: assuming1. 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) {
      happy_path;
    } else {
      error_c;
    }
  } else {
    error_b;
  }
} else {
  error_a;
}
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) {
  error_a;
}

if (!b) {
  error_b;
}

if (!c) {
  error_c;
}

happy_path
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.

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.

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.

Source Link
amon
  • 135.2k
  • 27
  • 292
  • 384

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.