Property-Based Testing

When a property fails, test.check shrinks the input to a minimal failing case. Combine with clojure.spec (stest/check) to test functions directly from their specs.

(require '[clojure.test.check :as tc]
         '[clojure.test.check.generators :as gen]
         '[clojure.test.check.properties :as prop]
         '[clojure.test.check.clojure-test :refer [defspec]])

;; Properties: claims that should hold for ALL generated inputs
(def reverse-twice-identity
  (prop/for-all [v (gen/vector gen/small-integer)]
    (= v (reverse (reverse v)))))
(tc/quick-check 100 reverse-twice-identity)    ; {:result true, :num-tests 100, ...}

(defspec sort-is-idempotent 200
  (prop/for-all [v (gen/vector gen/small-integer)]
    (= (sort v) (sort (sort v)))))

(defspec sort-preserves-elements 200
  (prop/for-all [v (gen/vector gen/small-integer)]
    (= (frequencies v) (frequencies (sort v)))))

;; Generators
(gen/sample gen/string-alphanumeric 5)
(gen/sample (gen/choose 1 6) 10)
(gen/sample (gen/elements [:red :green :blue]))
(def user-gen (gen/hash-map :name gen/string-alphanumeric :age (gen/choose 0 120)))
(gen/sample user-gen 3)
(gen/sample (gen/fmap #(* 2 %) gen/nat))                 ; even numbers
(gen/sample (gen/such-that not-empty (gen/vector gen/nat)))