Skip to content

@reiddraper's test.check (née simple-check), made Clojure/ClojureScript-portable DEPRECATED

Notifications You must be signed in to change notification settings

cemerick/double-check

Repository files navigation

double-check Build Status

DEPRECATED

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.

Why a fork?

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:

  1. ...always tracks test.check as closely as possible, with the only divergences being those necessary to ensure an API portable between Clojure and ClojureScript.
  2. ...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.
  3. ...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.
  4. ...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.
  5. ...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.

Releases and Dependency Information

Leiningen

Add this to your :dependencies:

[com.cemerick/double-check "0.6.1"]

Maven

<dependency>
  <groupId>com.cemerick</groupId>
  <artifactId>double-check</artifactId>
  <version>0.6.1</version>
</dependency>

Documentation

Migrating from simple-check or test.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 becomes clojure.test.check (note the dropping of 'core'). For all other things, just replace simple-check with clojure.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.

Examples

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.

clojure.test Integration

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

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.

See also...

Other implementations

Papers

Contributing

We can not accept pull requests. Please see CONTRIBUTING.md for details.

License

Copyright © 2013 Reid Draper and other contributors

Distributed under the Eclipse Public License, the same as Clojure.

About

@reiddraper's test.check (née simple-check), made Clojure/ClojureScript-portable DEPRECATED

Resources

Stars

Watchers

Forks

Packages

No packages published