• Stars
    star
    1,184
  • Rank 39,337 (Top 0.8 %)
  • Language
    JavaScript
  • License
    Other
  • Created about 10 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Generative testing for JavaScript

TestCheck.js Build Status

Generative property testing for JavaScript.

TestCheck.js is a library for generative testing of program properties, ala QuickCheck.

By providing a specification of the JavaScript program in the form of properties, the properties can be tested to remain true for a large number of randomly generated cases. In the case of a test failure, the smallest possible failing test case is found.

Getting started

Install testcheck using yarn

yarn add --dev testcheck

Or using npm

npm install --save-dev testcheck

Then require it into your testing environment and start testing.

const { check, gen, property } = require('testcheck');

const result = check(
  property(
    gen.int,
    x => x - x === 0
  )
)

Have a favorite test framework?

TestCheck.js is a testing utility and not a complete test-running framework. It doesn't replace test frameworks like AVA, Jasmine, or Mocha.

If you use AVA then check out ava-check, a testcheck AVA plugin.

const test = require('ava')
const { check, gen } = require('ava-check')

test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => {
  t.true(numA + numB === numB + numA)
}))

If you use Jasmine or Jest then check out jasmine-check, a testcheck Jasmine (or Jest) plugin.

require('jasmine-check').install()

describe('Maths', () => {
  check.it('addition is commutative', gen.int, gen.int, (numA, numB) => {
    expect(numA + numB).toEqual(numB + numA)
  })
})

If you use Mocha then check out mocha-testcheck, a testcheck Mocha plugin.

require('mocha-testcheck').install();
const { expect } = require('chai');

describe('Maths', () => {
  check.it('addition is commutative', gen.int, gen.int, (numA, numB) => {
    expect(numA + numB).to.equal(numB + numA)
  })
})

If you use Tape then check out tape-check, a testcheck Tape plugin.

const test = require('tape')
const { check, gen } = require('tape-check')

test('addition is commutative', check(gen.int, gen.int, (t, numA, numB) => {
  t.plan(1)
  t.equal(numA + numB, numB + numA)
}));

Type definitions

This module includes type definitions for Flow type and Typescript. Simply require or import this module and enjoy type suggestions and corrections.

Using TestCheck.js

See the complete API documentation for all available generators and utilities, or the Walkthrough Guide for a more thorough walkthrough.

Try it! Open the developer console while viewing the docs to follow along with the examples below.

Defining properties

A property is simply a function which is expected to always return true, we might also call these properties "assumptions" or "expectations".

For example, say we wanted to test the assumption that any number subtracted from itself will be 0, we could define this property as:

function (x) {
  return x - x === 0
}

Or as another example, let's determine that sorting an array is stable and idempotent, which is to say that sorting a sorted array shouldn't do anything. We could write:

function (arr) {
  var arrCopy = arr.slice()
  return deepEqual(arrCopy.sort(), arr.sort().sort())
}

That's really it! The only thing special about this property function is that it is pure, e.g. it relies only on the provided arguments to determine its return value (no other reading or writing!).

If you can start to describe your program in terms of its properties, then testcheck can test them for you.

Generating test cases

Once we've defined some properties, we generate test cases for each properties by describing the types of values for each argument.

For testing our first property, we need numbers:

gen.int

For the second, we need arrays of numbers

gen.array(gen.int)

There are a wide variety of value generators, we've only scratched the surface. We can generate random JSON with gen.JSON, pick amongst a set of values with gen.returnOneOf, nested arrays with ints gen.nested(gen.array, gen.int) and much more. You can even define your own generators with generator.then(), and gen.sized.

Checking the properties

Finally, we check our properties using our test case generator (in this case, up to 1000 different tests before concluding).

const result = check(
  property(
    // the arguments generator
    gen.int,
    // the property function to test
    x => x - x === 0
  ),
  { numTests: 1000 }
)

check runs through random cases looking for failure, and when it doesn't find any failures, it returns:

{ result: true, numTests: 1000, seed: 1406779597155 }

Smallest failing test

Let's try another property: the sum of two integers is the same or larger than either of the integers alone.

check(
  property(
    gen.int, gen.int,
    (a, b) => a + b >= a && a + b >= b
  )
)

check runs through random cases again. This time it found a failing case, so it returns:

{ result: false,
  failingSize: 2,
  numTests: 3,
  fail: [ 2, -1 ],
  shrunk:
   { totalNodesVisited: 2,
     depth: 1,
     result: false,
     smallest: [ 0, -1 ] } }

Something is wrong. Either:

  1. Our assumption is wrong (e.g. bug in our software).
  2. The test code is wrong.
  3. The generated test data is too broad.

In this case, our problem is that our generated data is too broad for our assumption. What's going on?

We can see that the fail case 2, -1 would in fact not be correct, but it might not be immediately clear why. This is where test case shrinking comes in handy. The shrunk key provides information about the shrinking process and most importantly, the smallest values that still fail: 0, -1.

We forgot about an edge case! If one of the integers is negative, then the sum will not be larger. This shrunken test case illustrated this much better than the original failing test did. Now we know that we can either improve our property or make the test data more specific:

check(property(
  gen.posInt, gen.posInt,
  (a, b) => a + b >= a && a + b >= b
));

With our correction, our property passes all tests.

Thinking in random distributions

It's important to remember that your test is only as good as the data being provided. While testcheck provides tools to generate random data, thinking about what that data looks like may help you write better tests. Also, because the data generated is random, a test may pass which simply failed to uncover a corner case.

"Testing shows the presence, not the absence of bugs"

— Dijkstra, 1969

Sampling Test Data

Visualizing the data check generates may help diagnose the quality of a test. Use sample and sampleOne to get a look at what a generator produces:

const { gen, sample, sampleOne } = require('testcheck')

sample(gen.int)
// [ 0, 0, 2, -1, 3, 5, -4, 0, 3, 5 ]

sampleOne(gen.int)
// -23

The Size of Test Data

Test data generators have an implicit size property, which could be used to determine the maximum value for a generated integer or the max length of a generated array. testcheck begins by generating small test cases and gradually increases the size.

So if you wish to test very large numbers or extremely long arrays, running check the default 100 times with maxSize of 200, you may not get what you expect.

Data relationships

Let's test an assumption that should clearly be wrong: a string split by another string always returns an array of length 1.

check(property(
  gen.asciiString.notEmpty(), gen.asciiString.notEmpty(),
  (str, separator) => str.split(separator).length === 1
))

Unless you got lucky, you probably saw this check pass. This is because we're testing for a relationship between these strings. If separator is not found in str, then this test passes. The second unrelated random string is very unlikely to be found within the first random string.

We could change the test to be aware of this relationship such that the separator is always contained within the str by using then().

check(property(
  gen.asciiString.notEmpty().then(str =>
    [ str, gen.substring(str).notEmpty() ]),
  ([ str, separator ]) => str.split(separator).length === 1
))

Now separator is a random substring of str and the test fails with the smallest failing arguments: [ ' ', ' ' ].

We can test this example out ourselves, with the value ' ' generated for both str and separator, we can run ' '.split(' ').length to see that we in fact get 2, not 1.

License

Copyright 2014-Present Lee Byron

TestCheck.js is distributed under the BSD-3-Clause license.

Atop the shoulders of giants

TestCheck.js is based on Clojure's test.check which is inspired by Haskell's QuickCheck. Many gracious thanks goes to all of the brilliance and hard work enabling this project to exist.

Clojure's test.check is Copyright Rich Hickey, Reid Draper and contributors and is distributed under the Eclipse Public License.

More Repositories

1

react-loops

React Loops works with React Hooks as part of the React Velcro Architecture
JavaScript
1,166
star
2

iterall

🌻 Minimal zero-dependency utilities for using Iterables in all JavaScript environments.
JavaScript
815
star
3

async-to-gen

⌛ Use async functions in your JavaScript today with speed and simplicity.
JavaScript
506
star
4

spec-md

📖 Additions to Markdown for writing specification documents
HTML
380
star
5

streamgraph

Processing applet which creates the images seen in the Streamgraph paper
Java
281
star
6

ecmascript-more-export-from

Proposal: add more export-from statements in ES7
153
star
7

mesh

Processing library for creating point meshes.
Java
97
star
8

rollup-plugin-flow

Rollup plugin for removing Flow type annotations.
JavaScript
80
star
9

4000

63
star
10

cocoa-oauth2

Cocoa library for handling oauth2
59
star
11

til

Today I Learned
JavaScript
55
star
12

interactive-script

Easy to write interactive scripts
JavaScript
52
star
13

ecmascript-iterator-hof

Higher Order Functions on Iterators
JavaScript
42
star
14

rollup-plugin-async

Transforms Async functions to generator functions before bundling.
JavaScript
40
star
15

ecmascript-reverse-iterable

ReverseIterable Interface Spec Proposal
JavaScript
30
star
16

keyboard

My keyboard config
30
star
17

anyconnect-dark

Dark OSX menu bar assets for Cisco AnyConnect VPN.
24
star
18

mocha-check

Generative property testing for Mocha
17
star
19

bigtype

💥 Type big stuff
HTML
17
star
20

huron.wedding

Our wedding site
HTML
16
star
21

centerclock

A generative abstract clock.
JavaScript
13
star
22

fb-notify

Objective-C
12
star
23

grunt-jest

Grunt task for running jest tests.
JavaScript
12
star
24

respectify

Replaces the term 'politically correct' with 'respectful of other people' because it makes you oddly happy.
JavaScript
12
star
25

jasmine-check

Generative property testing for Jasmine
11
star
26

loda-js

Use JavaScript functionally, you shall.
JavaScript
10
star
27

ofxSDL

Overrides the openFrameworks windowing system to use SDL rather than GLUT.
C++
9
star
28

unflowify

Browserify transform for removing Flow type annotations.
JavaScript
9
star
29

fb-cocoa

Objective-C
8
star
30

remark-comment

Remark plugin to support comments
JavaScript
7
star
31

bubbletype

JavaScript
7
star
32

advent-of-code-2016

Solutions to the Advent of Code 2016 puzzles
JavaScript
6
star
33

dotfiles

JavaScript
6
star
34

ecmascript-reduced

Proposal for short-circuiting Array.prototype.reduce()
JavaScript
4
star
35

leebyron.github.io

Who has business cards anymore?
TypeScript
4
star
36

shapetween

(ARCHIVED 2007) Processing Library for tween and shape curves
Java
4
star
37

Wesley

Snipes
JavaScript
3
star
38

dbmd

Dropbox Hosted Markdown based CMS
JavaScript
3
star
39

iFBG

3
star
40

chain-js

A reactive framework.
JavaScript
2
star
41

cocoa-fbg

2
star
42

of-xcode-templates

openFrameworks XCode Templates
C++
2
star
43

shitty-peg

A Shitty PEG Parser
JavaScript
2
star
44

codespaces-dotfiles

Shell
2
star
45

lisp

Let's Learn Lisp with Lee
JavaScript
1
star
46

phagos

Button Mashing Glutton
C++
1
star
47

nuri-dog

http://nuri.dog
HTML
1
star
48

cocoa-net-connection

Cocoa class which monitors net connectivity
Objective-C
1
star
49

lrumap

JavaScript
1
star