Don’t test implementation details

One of the biggest roadblocks I’ve personally run into in testing JavaScript—the thing that grinds my testing process to a halt—is fretting over implementation details and code coverage.
Today, I wanted to talk about why they don’t matter. Let’s dig in!
This article is part of an ongoing series of JavaScript testing. In past articles, we looked at buildless testing, an overview of testing approaches, how to write unit tests, how to organize tests, and TDD.


This content originally appeared on Go Make Things and was authored by Go Make Things

One of the biggest roadblocks I’ve personally run into in testing JavaScript—the thing that grinds my testing process to a halt—is fretting over implementation details and code coverage.

Today, I wanted to talk about why they don’t matter. Let’s dig in!

This article is part of an ongoing series of JavaScript testing. In past articles, we looked at buildless testing, an overview of testing approaches, how to write unit tests, how to organize tests, and TDD.

An example of an implementation detail

Looking at the calculator.js library that we’ve been testing throughout this series, we have functions to add(), subtract(), multiply(), and divide() some numbers.

// returns 10
let total = add(4, 6);

Let’s imagine that we wanted to make sure that if someone passed in a non-numeric value, the script didn’t break…

let total = add(4, 6, 'abc');

Under the hood, we might try to use parseFloat() convert numeric strings to a number.

We might also add a Number.isNaN() check before using a number, and skip it if it’s not a number.

/**
 * Add two or more numbers together
 * @param  {...Numbers} nums The numbers to add together
 * @return Number            The total
 */
function add (...nums) {
	let total = nums.length ? nums.shift() : 0;
	for (let num of nums) {
		num = parseFloat(num);
		if (Number.isNaN(num)) continue;
		total = total + num;
	}
	return total;
}

Now, if we pass in numeric strings, they get converted to numbers, while non-numeric strings get ignored.

// returns 10
let total = add(4, 6, 'abc');

// returns 6.15
let tax = multiply(123, '0.05');

Testing the new behavior

We can write tests to ensure that library behaves the way we would expect when strings are passed in…

it('Should skip non-numeric values', function () {
	expect(add(4, 6, 'abc')).to.equal(10);
	expect(add(4, 6, '8')).to.equal(18);
});

And if we’re using TDD, we would have written these first, then written our code.

But what happens when we get to the refactor phase?

Changing how the code works under-the-hood

Let’s say we look at this bit of code repeated in each of methods…

num = parseFloat(num);
if (Number.isNaN(num)) continue;

And decide we want to abstract it into some sort of internal function.

/**
 * Remove non-numeric strings from the array
 * @param  {Array} nums The original array
 * @return {Array}      The cleaned array
 */
function cleanNumbers (nums) {
	let cleaned = [];
	for (let num of nums) {
		num = parseFloat(num);
		if (Number.isNaN(num)) continue;
		cleaned.push(num);
	}
	return cleaned;
}

In our methods, we use the cleanNumbers() method to get a clean array before we do any math.

function add (...nums) {
	let total = nums.length ? nums.shift() : 0;
	nums = cleanNumbers(nums);
	for (let num of nums) {
		total = total + num;
	}
	return total;
}

Test behaviors, not implementation details

How would you modify your tests to account for this? You wouldn’t!

The external behavior of our library hasn’t changed. How you handle numeric strings is an implementation detail. As long as the behavior is the same, the way you get there doesn’t really matter (from a test perspective).

Sometimes folks will fixate on how to test the cleanNumbers() function, which is not exported from the library and thus can’t be accessed directly by the test. A lot of testing frameworks even have ways you can “peek in” to your library to do that.

Don’t do that!

The cleanNumbers() function is a detail. If you test it directly and change it in the future, you’ll need to rewrite your tests even though nothing about the external behavior of the library has changed.

For this reason, code coverage, the percentage of your library covered by a test, it also a bit of a meaningless metric.

Many testing libraries will include functions used in other functions as part of their coverage assessment now. Some will not. Either way, it doesn’t particularly matter.

If your tests cover 100 percent of the external behaviors of your library and fail for meaningful reasons, you’re good.

You can download the source code for today’s article on GitHub.

🎉 New Year Sale! Now through the New Year, get 40% off your first year of Lean Web Club membership. Level-up as a developer. Click here to learn more.


This content originally appeared on Go Make Things and was authored by Go Make Things


Print Share Comment Cite Upload Translate Updates
APA

Go Make Things | Sciencx (2024-12-17T14:30:00+00:00) Don’t test implementation details. Retrieved from https://www.scien.cx/2024/12/17/dont-test-implementation-details/

MLA
" » Don’t test implementation details." Go Make Things | Sciencx - Tuesday December 17, 2024, https://www.scien.cx/2024/12/17/dont-test-implementation-details/
HARVARD
Go Make Things | Sciencx Tuesday December 17, 2024 » Don’t test implementation details., viewed ,<https://www.scien.cx/2024/12/17/dont-test-implementation-details/>
VANCOUVER
Go Make Things | Sciencx - » Don’t test implementation details. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/12/17/dont-test-implementation-details/
CHICAGO
" » Don’t test implementation details." Go Make Things | Sciencx - Accessed . https://www.scien.cx/2024/12/17/dont-test-implementation-details/
IEEE
" » Don’t test implementation details." Go Make Things | Sciencx [Online]. Available: https://www.scien.cx/2024/12/17/dont-test-implementation-details/. [Accessed: ]
rf:citation
» Don’t test implementation details | Go Make Things | Sciencx | https://www.scien.cx/2024/12/17/dont-test-implementation-details/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.