test?
Bill Gates5 Questions Every Unit Test Must Answer â JavaScript Scene â Medium
Eric ElliottCompassionate entrepreneur on a mission to end homelessness. #jshomes Javascript, tech education, electronic music, photography, film, viral apps.Aug 29, 2015
5 Questions Every Unit Test Must Answer
How to Write Better Tests
Most Developers
Donât Know How to Test
Every developer knows we should write unit tests in order to prevent defects from being deployed to production.
What most developers donât know are the essential ingredients of every unit test. I canât begin to count the number of times Iâve seen a unit test fail, only to investigate and discover that I have absolutely no idea what feature the developer was trying to test, let alone how it went wrong or why it matters.
In a recent project of mine, we let a gigantic swath of unit tests enter the test suite with absolutely no description whatsoever of the testâs purpose. We have a great team, so I let my guard down. The result? We still have a ton of unit tests that only the author can really make sense of.
Luckily, weâre completely redesigning the API, and weâre going to throw the entire suite away and start from scratchâââotherwise, this would be priority #1 on my fix list.
Donât let this happen to you.
Why Bother with Test Discipline?
Your tests are your first and best line of defense against software defects. Your tests are more important than linting & static analysis (which can only find a subclass of errors, not problems with your actual program logic). Tests are as important as the implementation itself (all that matters is that the code meets the requirementâââhow itâs implemented doesnât matter at all unless itâs implemented poorly).
Unit tests combine many features that make them your secret weapon to application success:
- Design aid: Writing tests first gives you a clearer perspective on the ideal API design.
- Feature documentation (for developers): Test descriptions enshrine in code every implemented feature requirement.
- Test your developer understanding: Does the developer understand the problem enough to articulate in code all critical component requirements?
- Quality Assurance: Manual QA is error prone. In my experience, itâs impossible for a developer to remember all features that need testing after making a change to refactor, add new features, or remove features.
- Continuous Delivery Aid: Automated QA affords the opportunity to automatically prevent broken builds from being deployed to production.
Unit tests donât need to be twisted or manipulated to serve all of those broad-ranging goals. Rather, it is in the essential nature of a unit test to satisfy all of those needs. These benefits are all side-effects of a well-written test suite with good coverage.
The Science of TDD
The evidence says:
- TDD can reduce bug density.
- TDD can encourage more modular designs (enhancing software agility/team velocity).
- TDD can reduce code complexity.
Says science: There is significant empirical evidence that TDD works*.
Write Tests First
Studies from Microsoft Research, IBM, and Springer tested the efficacy of test-first vs test-after methodologies and consistently found that a test-first process produces better results than adding tests later. It is resoundingly clear: Before you implement, write the test.
Before you implement,
write the test.
Whatâs in a Good Unit Test?
OK, so TDD works. Write tests first. Be more disciplined. Trust the process⦠We get it. But how do you write a good unit test?
Weâre going to look at a very simple example from a real project to explore the process: The `compose()` function from the Stamp Specification.
Weâre going to use tape for the tests because of its crystal clarity and essential simplicity.
Before we can answer how to write a good unit test, first we have to understand how unit tests are used:
- Design aid: written during design phase, prior to implementation.
- Feature documentation & test of developer understanding: The test should provide a clear description of the feature being tested.
- QA/Continuous Delivery: The tests should halt the delivery pipeline on failure and produce a good bug report when they fail.
The Unit Test as a Bug Report
When a test fails, that test failure report is often your first and best clue about exactly what went wrongâââthe secret to tracking down a root cause quickly is knowing where to start looking. That process is made much easier when you have a really clear bug report.
A failing test should read
like a high-quality bug report.
Whatâs in a good test failure bug report?
- What were you testing?
- What should it do?
- What was the output (actual behavior)?
- What was the expected output (expected behavior)?
Start by answering âwhat are you testing?â:
- What component aspect are you testing?
- What should the feature do? What specific behavior requirement are you testing?
The `compose()` function takes any number of stamps (composable factory functions) and produces a new stamp.
To write this test, weâre going to work backwards from the end goal of any single test: To test a specific behavior requirement. In order for this test to pass, what specific behavior must the code produce?
What should the feature do?
I like to start by writing a string. Not assigned to anything. Not passed into any function. Just a clear focus on a specific requirement that the component must satisfy. In this case, weâll start with the fact that the `compose()` function should return a function.
A simple, testable requirement:
'compose() should return a function.'
Now weâll skip some stuff and flesh out the rest of the test. This string is serving as our goal. Stating it beforehand helps us keep our eye on the prize.
What component aspect are we testing?
What you mean by âcomponent aspectâ will vary from test to test, depending on the granularity required to provide adequate coverage of the component under test.
In this case, weâre going to test the return type of the `compose()` function to make sure it returns the right kind of thing, as opposed to `undefined` or nothing at all because it throws when you run it.
Letâs translate this question into test code. The answer goes into the test description. This step is also where weâll make our function call and pass the callback function that the test runner will invoke when the tests run:
test('', assert => {
});
In this case, weâre testing the output of the compose function:
test('Compose function output type.', assert => {
});
And of course we still need our first description. It goes inside the callback function:
test('Compose function output type.', assert => {
'compose() should return a function.'
});
What is the Output (Expected and Actual)?
`equal()` is my favorite assertion. If the only available assertion in every test suite was `equal()`, almost every test suite in the world would be better for it. Why?
Because `equal()`, by nature answers the two most important questions every unit test must answer, but most donât:
- What is the actual output?
- What is the expected output?
If you finish a test without answering those two questions, you donât have a real unit test. You have a sloppy, half-baked test.
If you take only one thing from this article, let it be this:
Equal is your new default assertion.
It is the staple of every good test suite.
All those fancy assertion libraries with hundreds of different fancy assertions are destroying the quality of your tests.
A Challenge
Want to get much better at writing unit tests? For the next week, try writing every single assertion using `equal()` or `deepEqual()`, or their equivalents in your assertion library of choice. Donât worry about the quality impact on your suite. My money says that the exercise will improve it dramatically.
What does this look like in code?
const actual = '';
const expected = '';
The first question really does double duty in a test failure. By answering the question, your code also answers another:
const actual = '';
Itâs important to note that the `actual` value must be produced by exercising some of the componentâs public API. Otherwise, the test has no value. Iâve seen test suites that are so overwhelmed with mocks and stubs and bells and whistles that some of the tests never exercised any of the code supposedly being tested.
Letâs return to the example:
const actual = typeof compose();
const expected = 'function';
You could build an assertion without specifically assigning values to variables called `actual` and `expected`, but I recently started to specifically assign values to variables called `actual` and `expected` in every test and found that it made my tests easier to read.
See how it clarifies the assertion?
assert.equal(actual, expected,
'compose() should return a function.');
It separates the âhowâ from the âwhatâ in the test body.
- Want to know how we got the results? Look at the variable assignments.
- Want to know what weâre testing for? Look at the assertionâs description.
The result is that the test itself reads as easily as a high quality bug report.
Letâs look at the whole thing in context:
Next time you write a test, remember to answer all the questions:
- What are you testing?
- What should it do?
- What is the actual output?
- What is the expected output?
- How can the test be reproduced?
The last question is answered by the code used to derive the `actual` value.
A Unit Test Template:
Thereâs a whole lot more to using unit tests well, but knowing how to write a good test goes a long way.
Watch the webcast recording,
TDD in ES6 &Â React
& much more with the
Lifetime Access Pass
Eric Elliott is the author of âProgramming JavaScript Applicationsâ (OâReilly), and âLearn JavaScript Universal App Development with Node, ES6, & Reactâ. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.
He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.
Eric Elliott
Compassionate entrepreneur on a mission to end homelessness. #jshomes Javascript, tech education, electronic music, photography, film, viral apps.
JavaScript Scene
To submit, DM your proposal to @JS_Cheerleader on Twitter
- Share
JavaScript Scene, when you sign up for Medium.
Learn moreNever miss a story from
JavaScript Scene