Sie sind auf Seite 1von 6

Unit Tests for Random Functions

Shotgun PostAmp Episode 3

Eric Elliott
Nov 7, 2019 · 5 min read

Photo: Brandon Bailey — Midnight Cowboy (CC-BY-2.0)

Shotgun is video series that lets you ride shotgun with me while I tackle real programming
challenges for real apps and libraries. The videos are only available to members of
EricElliottJS.com, but I’m journaling the adventures here.

One of the challenges you’ll face when you use TDD long enough is the question: “How
do I test random functions?”
In this episode of the Shotgun series, we encountered this issue, because we want to
generate a randomized schedule to send out automated social media updates.

It’s important to be careful with our time if we’re going to be productive, so it’s great
that PostAmp allows us to batch create a bunch of social media posts at once, but we
don’t want to dump 30 tweets on our followers in a great big batch once per week.
People would quickly unfollow because you’re blowing up their notifications.

Instead, wouldn’t it be nice if we could batch them all up, and PostAmp could generate
a randomized schedule to post them for us throughout the week?

But to do that, we’re going to need to write some functions that are going to produce
random output.

Pure functions are the easiest kinds of functions to unit test. Just call them, and then
assert that the output is what you expected it to be.

But what about functions with random output? We can’t assert that the output is what
we expect it to be. Instead, we’ll need to assert that the output obeys some constraints.

The first step in the process of generating our random post schedule, we’re going to
need to break the problem down. Let’s start by generating a random number inside a
given range. The signature will look like this:

randomNumber = (start: Number, end: Number) => Number

Our constraints will be pretty simple:

Given no start and end, generate a random number greater than or equal to start

Given start and end, generate a random number less than or equal to end

When we’re dealing with random numbers, we want to be sure not just that a single
number conforms to our constraints. We want a fair degree of certainty that all
numbers generated will conform to our constraints.

To do that, I usually generate a big batch of them and test them all. Here’s what I came
up with:
import { describe } from 'riteway';
import { randomNumber } from './utils.js';

describe('randomNumber', async assert => {


const start = 3;
const end = 20;
const numbers = Array.from({ length: 100 }, () =>
randomNumber(start, end));

assert({
given: 'start, end',
should: 'generate a random number greater than or equal to
start',
actual: numbers.every(n => n >= start),
expected: true
});

assert({
given: 'start, end',
should: 'generate a random number less than or equal to end',
actual: numbers.every(n => n <= end),
expected: true
});
});

First, Write ONE Test, Make it Pass


If you watch the video, you’ll probably notice a couple things. First, my test
descriptions were wrong. I said, “given no arguments” — I’ve corrected that in this blog
post. Oops.

But you’ll also notice that I wrote one test at a time. I started with:

assert({
given: 'start, end',
should: 'generate a random number greater than or equal to start',
actual: numbers.every(n => n >= start),
expected: true
});

Before moving on to the next test, I watched this one fail, then wrote the
implementation:

export const randomNumber = (start, end) =>


Math.round(Math.random() * (end - start) + start);
Then I wrote the next test:

assert({
given: 'start, end',
should: 'generate a random number less than or equal to end',
actual: numbers.every(n => n <= end),
expected: true
});

The next test passed already because I wrote the whole implementation. Sometimes, if
I know what I want the implementation to look like, I’ll write it, then make a change to
break the test, then watch the test fail, then fix the code and watch it pass again.

Why would I do all that? To test the tests!

If you haven’t seen a test fail, you don’t know if the


test works.
If you watch the video, you’ll also notice that I made a silly mistake towards the end,
when I tried to break the end test, I changed the code like this:

export const randomNumber = (start, end) =>


Math.round(Math.random() * (5 - start) + start);

Of course, that didn’t break the test, because 5 is still in the valid range. To try to hunt
down that problem, I logged the actual array of outputs to the console. The wiser of
you may have guessed that I did that on purpose for two reasons:

1. I wanted to show that test debugging technique, and

2. I wanted to show you that everybody makes mistakes.

Good guess! But that was a genuine mistake, and a particularly silly one. It was just a
happy accident that we got those extra lessons. Remember, everybody makes mistakes.
If we didn’t, we wouldn’t need TDD.
TDD acts like a double entry accounting ledger. For every change, you have two
records of the change: In the code, and in your tests. If you can get both records to
agree, you have double the confidence that you’ve written the code correctly.

Next Steps
Members can watch the new episode on EricElliottJS.com. If you’re not a member,
now’s a great time to see what you’ve been missing!

Start your free lesson on EricElliottJS.com

. . .

Eric Elliott is the author of the books, “Composing Software” and “Programming
JavaScript Applications”. As co-founder of EricElliottJS.com and DevAnywhere.io, he
teaches developers essential software development skills. He builds and advises
development teams for crypto projects, and 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 enjoys a remote lifestyle with the most beautiful woman in the world.

JavaScript Tdd Technology Code Programming


About Help Legal

Get the Medium app

Das könnte Ihnen auch gefallen