/test.chuck

A utility library for test.check

Primary LanguageClojureEclipse Public License 1.0EPL-1.0

test.chuck

test.chuck is a utility library for test.check.




       (this space intentionally left blank)



Obtention

Leiningen dependency coordinates:

[com.gfredericks/test.chuck "0.1.16"]

Usage

General Helpers

(require '[com.gfredericks.test.chuck :as chuck])

times

A helper function for being able to scale your test run count with an environment variable. To use with defspec, simply wrap your test-count argument in a call to times:

(defspec foo-bar-test (chuck/times 20)
  ...)

This will normally run the test 20 times, but if you set the TEST_CHECK_FACTOR environment variable to e.g. 3.5, it will run the tests 70 times.

Generators

(require '[com.gfredericks.test.chuck.generators :as gen'])

There are a few minor generators and helpers, see the docstrings for details:

  • for (described below)
  • string-from-regex
  • subsequence (for subsets and similar)
  • cap-size
  • partition
  • map->hash-map
  • bounded-int
  • double

for

A macro that uses the syntax of clojure.core/for to provide the functionality of gen/bind, gen/fmap, gen/such-that, and gen/tuple:

(gen'/for [len gen/nat
           bools (gen/vector gen/boolean len)]
  [len bools])

(gen/sample *1)
;; => ([0 []]
;;     [0 []]
;;     [2 [false true]]
;;     [3 [true false true]]
;;     [1 [true]]
;;     [5 [true true false false true]]
;;     [2 [false false]]
;;     [1 [true]]
;;     [8 [true false false true false false false false]]
;;     [1 [true]])
(gen'/for [:parallel [n1 gen/nat
                      n2 gen/nat]
           :when ^{:max-tries 20} (coprime? n1 n2)
           :let [product (* n1 n2)]]
  {:n product, :factors [n1 n2]})

(gen/sample *1)
;; => ({:n 1, :factors [1 1]}
;;     {:n 0, :factors [1 0]}
;;     {:n 2, :factors [1 2]}
;;     {:n 0, :factors [0 1]}
;;     {:n 6, :factors [3 2]}
;;     {:n 20, :factors [4 5]}
;;     {:n 0, :factors [1 0]}
;;     {:n 4, :factors [1 4]}
;;     {:n 24, :factors [3 8]}
;;     {:n 14, :factors [2 7]})

string-from-regex

string-from-regex is a suspiciously robust generator that will generate strings matching a regular expression:

user> (gen/sample (gen'/string-from-regex #"([☃-♥]{3}|B(A|OO)M)*"))
(""
 "☍♛☽"
 ""
 "♂♡☱BAM"
 "♥☩♏BAMBAM"
 ""
 "☓☪☤BAMBAMBOOMBOOM☑☔☟"
 ""
 "BOOM☻☘☌☏☜♋BAM♑♒♛BAMBAM"
 "BOOMBAM♅☧♉☎☐♘BOOM☥♜☐")

It does not work with every regular expression, but its goal is to correctly recognize (and report) the usage of unsupported features, and to handle supported features in a comprehensive way.

Shrinking

Generated strings shrink in a natural way:

(def gen-cool-string
  (gen'/string-from-regex
   #"This string has (1 [A-Z]|[2-9]\d* [A-Z]'s)((, (1 [A-Z]|[2-9]\d* [A-Z]'s))*, and (1 [A-Z]|[2-9]\d* [A-Z]'s))?\."))

(def bad-prop
  (prop/for-all [s gen-cool-string]
    (not (re-find #"1 F" s))))

(t.c/quick-check 1000 bad-prop)
=>
{:fail ["This string has 6309694848500700538 H's, 79102649012623413352 F's, 1 F, 59860 U's, 1 T, 1 W, 1 B, and 1 M."],
 :failing-size 26,
 :num-tests 27,
 :result false,
 :seed 1418877588316,
 :shrunk {:depth 8,
          :result false,
          :smallest ["This string has 1 A, 1 F, and 1 A."],
          :total-nodes-visited 27}}
Unsupported regex features

Some of these could be supported with a bit of effort.

  • All flags: (?i), (?s), etc.
  • Lookahead and lookbehind
  • Reluctant and Possesive quantifiers: X??, X*+, etc.
    • I'm not sure what these would mean anyhow
  • Anchors: \b, ^, \A, $...
  • Backreferences
    • This is tricky at least because it introduces the possibility of unmatchable expressions
  • Character class intersections
  • The hex syntax for unicode characters outside the BMP: \x{10001}
  • Named character classes: \p{IsAlphabetic}, \P{ASCII}, ...

Properties

com.gfredericks.test.chuck.properties/for-all is an alternative to clojure.test.check.properties/for-all that uses the for macro to interpret the binding clauses:

(require [com.gfredericks.test.chuck.properties :as prop'])

(prop'/for-all [a gen/pos-int
                :when (even? a)
                :let [b (/ a 2)]
                xs (gen/vector gen/int b)]
  (= (count xs) b))

Alternate clojure.test integration

A macro that allows you to use clojure.test/is and clojure.test/are with test.check, letting you keep the side-effecting style that is often used with clojure.test.

More details in this blog post.

Contributing

I welcome pull requests for any test.check utility that seems halfway reasonable.

Acknowledgments

  • @lackita for creating com.gfredericks.test.chuck.clojure-test
  • @weavejester for creating the original regex->string code
  • @miner for various help with the string-from-regex generator

License

Copyright © 2014 Gary Fredericks

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.