/simple-check

QuickCheck for Clojure

Primary LanguageClojure

simple-check

simple-check is a Clojure property-based testing tool inspired by QuickCheck. The core idea of simple-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.

Installation

Leiningen

[reiddraper/simple-check "0.5.3"]

Maven

<dependency>
  <groupId>reiddraper</groupId>
  <artifactId>simple-check</artifactId>
  <version>0.5.3</version>
</dependency>

Documentation

Examples

Let's say we're testing a sort function. We want 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:

(require '[simple-check.core :as sc])
(require '[simple-check.generators :as gen])
(require '[simple-check.properties :as prop])

(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? simple-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/such-that 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 simple-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

There is a macro called defspec that allows you to succinctly write properties that run under the clojure.test runner, for example:

(defspec first-element-is-min-after-sorting ;; the name of the test
         100 ;; the number of iterations for simple-check to test
         (prop/for-all [v (such-that not-empty (gen/vector gen/int))]
           (= (apply min v)
              (first (sorted v)))))

See more examples in core_test.clj.

Build Status

Build Status

Release Notes

Release notes for each version are available in CHANGELOG.markdown.

See also...

Other implementations

Papers

License

Copyright © 2013 Reid Draper

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