I came across Generative Testing in Clojure with spec
notion and would like to learn about it.
Also providing some examples would be very useful.
I came across Generative Testing in Clojure with spec
notion and would like to learn about it.
Also providing some examples would be very useful.
You may find it easiest to get started looking at clojure/test.check
before you dive into Clojure Spec
. From the project page:
(require '[clojure.test.check :as tc])
(require '[clojure.test.check.generators :as gen])
(require '[clojure.test.check.properties :as prop])
(def sort-idempotent-prop
(prop/for-all [v (gen/vector gen/int)]
(= (sort v) (sort (sort v)))))
(tc/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' inputs that still fail. 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)))))
(tc/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]]}}
As introductory reading we've got the Rationale and Overview along with the Guide which should provide you with information both about the why and the how.
If you'd like a somewhat complex example, we can take the
string->semantic-version
function ofleiningen.release
:It takes a string and tries to parse it into a program-readable map representing the version number of some artifact. A spec for it could look like:
First some dependencies
then a helper macro
followed by a definition of a semantic version
and some helper-specs
for the definition of the keys in the resulting map
and the map itself
followed by the function spec:
By now we can let Clojure Spec generate test data and feed it into the function itself in order to test whether it meets the constraints we've put up for it:
This tells us that out of the 1000 test cases spec generated for us the function passed every single one.