unModified()

Break stuff. Now.

Simplify Code Coverage With One Simple Trick

Eliminating code complexity using native APIs

February 19, 2018

In the last two-ish months, I've been converting a library and its plugins from being in separate projects into a monorepo. This was done to be able to maintain all of the assets in one place. One of the efforts involved was adding code coverage tracking for all packages, to ensure tests cover all the code that's written. Now code coverage can be a pain, but I discovered one simple trick to simplify code coverage.

No program is possible without conditionals and state. They're effectively what defines a program - a routine that makes decisions based on held data. Most people think code coverage is a useless metric because for the numbers to make sense, all execution branches and permutations of state must be covered. This requires an ungodly amount of time and test code that it becomes a cost-benefit hazard.

But not always...

const oldArray = [1, 2, 3, 4, 5]
const newArray = []

for(let i = 0, i < oldArray.length, i++){
  if(oldArray[i] % 2) continue
  else newArray.push(oldArray[i])
}

// Do stuff with newArray

The example builds an array of even numbers from an array of numbers. For coverage to make sense, tests must be written to cover each branch of the if(oldArray[i] % 2) conditional. But the root of the problem isn't the checking for odd and even numbers. It's the use of if-else which causes execution to branch out. What if we can remove execution branching without removing the conditional?

const oldArray = [1, 2, 3, 4, 5]
const newArray = oldArray.filter(v => !(v % 2))

// Do stuff with newArray

BOOM! We just eliminated the branching, but not the conditional.

The branchy code has been replaced with a native API call. Code will now execute linearly and will not branch out. This makes writing tests and code coverage a bit cleaner since we don't have branches to deal with. However, branching did not really go away. We just pushed it off to the native API, whose implementation is none of our concern. If you look at the polyfill implementation of array.filter, which is a close approximate of the native implementation, you'll see that it does the branching.

Conclusion

If you want to have a sane test-writing experience, meaningful code coverage report, and just less code to deal with overall, push off as much of your implementation to native APIs.