Write Great Unit Tests by Writing Unit Tests that Fail

Written by chrisnorthfield | Published 2022/06/05
Tech Story Tags: software-testing | software-design | software-development | software-qa | unit-testing | javascript | debugging | getsentry | web-monetization

TLDRIn order for any test that has never failed has no value, it must have failed at least once. Craig Livings made a controversial statement in a controversial LinkedIn post one year ago. The takeaway is that failing our tests is such an important skill. When writing tests, we must know they have failed, so we know they add value. This is why it’s so important to spot these mistakes when more complex code is more complex testing when more testing is more difficult to spot.via the TL;DR App

Yes, seriously…

Around one year ago, I came across this LinkedIn post by Craig Livings who posted this controversial statement:

Any test that has never failed adds no value #tdd #agile

What it means is any test that has never failed has no value, and therefore, can be deleted. In order for any test to add value, it must have failed at least once.

Let’s see why failing our tests is such an important skill…

Why must our tests fail?

Let us say we have written some code and some supporting unit tests. We’ve done good right? The tests have never failed, but we now have unit tests that support our code! Or at least we think we have…

Let’s look at some examples. One of a test that has not failed and one that has.

The test that has never failed

This example is a common scenario where we are just focused on writing a test that passes. We have no intention to make it fail.

We will use the following JavaScript function:

function sum(a, b) {
  return a + b;
}

module.exports = sum;

We have written the following Jest test to verify our sum function adds two numbers together:

const sum = require('./sum');

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

We run the test and it passes ✅

That’s it. We’re happy the test has passed so we move on.

Test that has failed

In this example, we use exactly the same code. However, this time we make sure the test fails to prove its value.

Using the same function:

function sum(a, b) {
  return a + b;
}

module.exports = sum;

And using the same test:

const sum = require('./sum');

test('two plus two is four', () => {
  expect(2 + 2).toBe(4);
});

We run the test and it passes ✅

But this time we want the test to fail. So we decide to modify the code:

function sum(a, b) {
  return 0;
}

module.exports = sum;

We re-run the test and we get another pass ✅

But wait… this should have failed. We removed the code that adds the numbers together.

We can see the reason the test still passed is that the test itself is incorrect. The test was not calling sum.

So we update the test:

const sum = require('./sum');

test('two plus two is four', () => {
  expect(sum(2, 2)).toBe(4);
});

We re-run the test and it fails ❌

Ok, great! Now let’s put our code back:

function sum(a, b) {
  return a + b;
}

module.exports = sum;

And we re-run the test and it passes ✅

Excellent, now our test has value because we have ensured the test is validating the correct thing.

The example is a simple one. However, when testing more complex code, it is difficult to spot these mistakes. This is why it’s so important we make sure our tests fail before we make them pass — so we have confidence in what we are validating.

The takeaway

When writing tests, we must know they have failed, so we know they add value.

Do you agree?

First published here


Written by chrisnorthfield | Software engineer. I'm passionate about the ocean too!
Published by HackerNoon on 2022/06/05