Adding tests

If bugs arise, we decide to make changes, or extend functionality, it would be nice if we can run a single command that runs some checks against our code so that we can be confident that we're not unintentionally breaking anything. We can lump all our node -p checks from the main recipe into a single bash (or batch) file, but there's a more standard and elegant approach.

Let's write some tests.

First, we'll need a test library; let's use the tap module. The tap tool is simple; it doesn't require it's own test runner, has built-in coverage analysis, and outputs TAP the Test Anything Protocol which is used across many languages:

npm install --save-dev tap 

Remember that we're installing with --save-dev because this dependency will not be required in production.

Now, assuming that we're in the hsl-to-hex-folder, let's create a test folder:

mkdir test 
Test writing
For an excellent article on test writing and TAP output, check out Eric Elliot's blog post Why I use Tape instead of Mocha & so should you, at
https://medium.com/javascript-scene/6aa105d8eaf4.

Now, let's create an index.js file with the following code in the test folder:

var hsl = require('../')
var test = require('tap').test

test('pure white', function (assert) {
var expected = '#ffffff'
var actual = hsl(0, 100, 100)
var it = 'max saturation and luminosity should return pure white'
assert.is(actual, expected, it)
assert.end()
})

test('medium gray', function (assert) {
var expected = '#808080'
var actual = hsl(0, 0, 50)
var it = '0% saturation, 50% luminosity should be medium gray'
assert.is(actual, expected, it)
assert.end()
})

test('hue - red', function (assert) {
var expected = '#ff0000'
var actual = hsl(0, 100, 50)
var it = '0deg should be red'
assert.is(actual, expected, it)
assert.end()
})

test('hue - blue', function (assert) {
var expected = '#0000ff'
var actual = hsl(240, 100, 50)
var it = '240deg should be blue'
assert.is(actual, expected, it)
assert.end()
})

test('hue - cyan', function (assert) {
var expected = '#00ffff'
var actual = hsl(180, 100, 50)
var it = '180deg should be cyan'
assert.is(actual, expected, it)
assert.end()
})

test('degree overflow', function (assert) {
var expected = hsl(1, 100, 50)
var actual = hsl(361, 100, 50)
var it = '361deg should be the same as 1deg'
assert.is(actual, expected, it)
assert.end()
})

test('degree underflow', function (assert) {
var expected = hsl(-1, 100, 50)
var actual = hsl(359, 100, 50)
var it = '-1deg should be the same as 359deg'
assert.is(actual, expected, it)
assert.end()
})

test('max constraint', function (assert) {
var expected = hsl(0, 101, 50)
var actual = hsl(0, 100, 50)
var it = '101% should be the same as 100%'
assert.is(actual, expected, it)
assert.end()
})

test('max constraint', function (assert) {
var expected = hsl(0, -1, 50)
var actual = hsl(0, 0, 50)
var it = '-1% should be the same as 0%'
assert.is(actual, expected, it)
assert.end()
})

In the package.json file, we'll edit the scripts.test field to read as follows:

"test": "npm run lint && tap --cov test",

We can see whether our tests are passing by running npm test:

npm test 

We also get to see a coverage report that was enabled with the --cov flag:

Coverage
Coverage is a percentage of the amount of logic paths that were touched by our tests. This can be measured in several ways; for instance, did we cover all the if/else branches? Did we cover every line of code? This can provide a sort of quality rating for our tests. However, there are two things to consider when it comes to coverage. First, 100% coverage does not equate to 100% of possible scenarios. There can be some input that causes our code to crash or freeze. For example, what if we passed in a hue of Infinity. In our case, we've handled that scenario but haven't tested it, yet we have 100% coverage. Secondly, in many real-world cases, getting the last 20% of coverage can become the most resource-intensive part of development, and it's debatable whether that last 20% will deliver on the time and effort investment required.