I strongly suggest migrating to use test.check, which now provides Clojure and ClojureScript APIs. Thanks to all contributors for helping with this stopgap until test.check made that transition.
double-check is a fork of @reiddraper's test.check (née simple-check) library, a property-based testing tool inspired by QuickCheck. The core idea of test.check (and thus, double-check) is that instead of enumerating expected input and output for unit tests, you write properties about your function that should hold true for all inputs. This lets you write concise, powerful tests.
While test.check is dedicated to remaining a Clojure-only API (at least for now?) double-check is written using cljx, and thus provides an API that is portable between Clojure and ClojureScript. This approach has already uncovered significant bugs in ClojureScript itself, and can do the same for your programs.
Please note that this fork:
- ...always tracks test.check as closely as possible, with the only divergences being those necessary to ensure an API portable between Clojure and ClojureScript.
- ...is not a rewrite. The move to cljx yields minimal changes compared to the test.check baseline; 100% of the core logic flows from it. There's nothing novel here.
- ...is not hostile in any way to test.check, @reiddraper, etc. (It's actually @reiddraper-approved! :-P) It exists solely to make it possible to apply test.check's testing approach to ClojureScript libraries and applications, not to supplant or compete with test.check. In particular, the core abstractions and generator/shrinking implementation defined in test.check are considered canonical. If test.check eventually provides equivalent functionality for the ClojureScript side of the house, this project will be shuttered.
- ...does not make any guarantees about 100% API compatibility with test.check, though it is based upon and tracks it. i.e. you should not expect to be able to move from test.check to double-check (or vice versa) in a Clojure project with no changes. Except for the (slightly) different namespaces, no changes are known to be required right now, but that may change to maximize runtime target portability.
- ...retains the
clojure.test.check.*
namespace structure, despite the name of this repo. This is to make tracking easier, and to allow users to share/port examples and usage of each package back and forth with a minimum of pain.
A word on versioning: version numbers will track test.check version numbers as
well, using a suffixed classifier (e.g. 0.1.2 turns into 0.1.2-1) to indicate
local changes made in between test.check releases. SNAPSHOT
version numbers
will be the same as test.check's.
Add this to your :dependencies
:
[com.cemerick/double-check "0.6.1"]
<dependency>
<groupId>com.cemerick</groupId>
<artifactId>double-check</artifactId>
<version>0.6.1</version>
</dependency>
- API Docs
- Generator writing guide
- Examples (some of these may refer to test.check):
- Blog posts and videos (some of these may refer to simple-check):
In order to migrate from simple-check or test.check to double-check, you'll need to do two things:
-
Update project.clj
In your
project.clj
replace[reiddraper/simple-check "$VERSION"]
or[org.clojure/test.check "$VERSION"]
with[com.cemerick/double-check "$LATEST_VERSION_HERE"]
(note: your version numbers may be different). -
Update namespace declarations
Update your namespaces:
simple-check.core
becomesclojure.test.check
(note the dropping of 'core'). For all other things, just replacesimple-check
withclojure.test.check
. Let's make it easy:find test -name '*.clj' -print0 | xargs -0 sed -i.bak \ -e 's/simple-check.core/clojure.test.check/' \ -e 's/simple-check/clojure.test.check/'
Review the updates.
Let's say we're testing a sort function. We want to check that that our sort
function is idempotent, that is, applying sort twice should be equivalent to
applying it once: (= (sort a) (sort (sort a)))
. Let's write a quick test to
make sure this is the case:
(ns double-check.demos
(:require [cemerick.double-check.core :as sc]
[cemerick.double-check.generators :as gen]
[cemerick.double-check.properties :as prop :include-macros true]))
(def sort-idempotent-prop
(prop/for-all [v (gen/vector gen/int)]
(= (sort v) (sort (sort v)))))
(sc/quick-check 100 sort-idempotent-prop)
;; => {:result true, :num-tests 100, :seed 1382488326530}
In prose, this test reads: for all vectors of integers, v
, sorting v
is
equal to sorting v
twice.
What happens if our test fails? test.check will try and find 'smaller' input that still fails. This process is called shrinking. Let's see it in action:
(def prop-sorted-first-less-than-last
(prop/for-all [v (gen/not-empty (gen/vector gen/int))]
(let [s (sort v)]
(< (first s) (last s)))))
(sc/quick-check 100 prop-sorted-first-less-than-last)
;; => {:result false, :failing-size 0, :num-tests 1, :fail [[3]],
:shrunk {:total-nodes-visited 5, :depth 2, :result false,
:smallest [[0]]}}
This test claims that the first element of a sorted vector should be less-than
the last. Of course, this isn't true: the test fails with input [3]
, which
gets shrunk down to [0]
, as seen in the output above. As your test functions
require more sophisticated input, shrinking becomes critical to being able
to understand exactly why a random test failed. To see how powerful shrinking
is, let's come up with a contrived example: a function that fails if its
passed a sequence that contains the number 42:
(def prop-no-42
(prop/for-all [v (gen/vector gen/int)]
(not (some #{42} v))))
(sc/quick-check 100 prop-no-42)
;; => {:result false,
:failing-size 45,
:num-tests 46,
:fail [[10 1 28 40 11 -33 42 -42 39 -13 13 -44 -36 11 27 -42 4 21 -39]],
:shrunk {:total-nodes-visited 38,
:depth 18,
:result false,
:smallest [[42]]}}
We see that the test failed on a rather large vector, as seen in the :fail
key. But then test.check was able to shrink the input down to [42]
, as
seen in the keys [:shrunk :smallest]
.
To learn more, check out the documentation links.
The clojure.test.check.clojure-test/defspec
macro allows you to succinctly write
properties that run under clojure.test
(or
clojurescript.test, as
appropriate). For example:
(defspec first-element-is-min-after-sorting ;; the name of the test
100 ;; the number of iterations for test.check to test
(prop/for-all [v (gen/not-empty (gen/vector gen/int))]
(= (apply min v)
(first (sort v)))))
This defines a standard clojure.test / clojurescript.test test, which can be
invoked directly to run only it. Or, you can run all of the tests in a
namespace or the entire environment with the test-ns
and run-all-tests
utility functions in clojure.test and clojurescript.test.
See more examples in core_test.clj
.
Release notes for each version are available in
CHANGELOG.markdown
. Changes that are specific to
double-check (i.e. that don't flow from upstream changes in test.check) are
noted as such.
We can not accept pull requests. Please see CONTRIBUTING.md for details.
Copyright © 2013 Reid Draper and other contributors
Distributed under the Eclipse Public License, the same as Clojure.