-
Notifications
You must be signed in to change notification settings - Fork 129
Facts v2
Facts are the smallest form Midje checks. Here's a fact with one checkable:
(fact (+ 1 1) => 2)
Here's a slightly more complicated fact:
(fact "addition has a unit element, 0, such that (+ x 0) => x"
(+ 0 0) => 0
(+ 1 0) => 1
(+ -1 0) => -1
(+ 100000 0) => 100000)
The doc string makes a universal claim, which Midje cannot verify. It can, however, evaluate and check the four checkables. Seeing them succeed will give you confidence in the claim (at least until you learn about arithmetic overflow).
In this user guide, fact doc strings are referred to as "descriptions", because they're stored in fact metadata under the key :midje/description
.
Fact descriptions are used to create failure messages:
user=> (fact "Twice two is three"
(* 2 2) => 3)
FAIL "Twice two is three" at (NO_SOURCE_PATH:2)
Expected: 3
Actual: 4
false
Notice that the result of the fact is false
. The result is true
only if all predictions come true.
Checkables don't have to be at the top level. Here's a checkable nested inside a let
:
(fact
(let [expected 2]
(+ 1 1) => expected
(* 2 1) => expected
(- 3 1) => expected))
Notice that the checkables can use lexically scoped symbols.
Facts can be nested arbitrarily deeply:
user=> (facts "about arithmetic"
(fact "there is addition"
(+ 1 1) => 2)
(fact "about subtraction"
(- 1 1) => 0)
;; Couldn't think of a fact name for this...
1 => pos?
-1 => neg?)
true
Notice that the outermost structure was named facts
. That's not required: it's just a synonym for fact
.
All the nested descriptions are used to create the failure output:
user=> (facts "about arithmetic"
(fact "twice two is three"
(+ 2 2) => 3))
FAIL "about arithmetic - twice two is three" at (NO_SOURCE_PATH:3)
Facts can contain tables. That looks like this:
(tabular "ways to arrive at 2"
(fact
(?op ?left ?right) => 2)
?op ?left ?right
+ 1 1
* 1 2
/ 4 2)
true
That form has three parts: the symbol tabular
(and optional description), an ordinary fact with particular symbols marked as special, and a table that gives values to substitute for special symbols.
The above tabular fact is actually expanded into something like this form:
(fact "ways to arrive at 2"
(fact
(+ 1 1) => 2)
(fact
(* 1 2) => 2)
(fact
(/ 4 2) => 2))
While that's not wildly efficient, it allows much of the power of macroexpansion.
The hedging above ("something like this form") is because failure messages provide more information about what substitution was made than a simple expansion would.
Generative tests that use facts can created using the for-all
construct (added in 1.9.0-alpha11
)
(require '[clojure.test.check.generators :as gen])
(defn my-keys [a-map] (map second a-map))
(for-all
[str-map (gen/map gen/keyword gen/string)]
{:max-size 10
:num-tests 15
:seed 1510160943861}
(fact "extracted keys are strings"
(my-keys str-map) => (has every? string?))
(fact "my-keys matches keys behavior"
(my-keys str-map) => (keys str-map)))
The form has three parts: the symbol for-all
(and optional description and options map), the binding vector where symbols are bound to example values generated from the generators, and the body, which should contain one or more fact
expressions.
Facts can have metadata. It's described completely in syntax and a little semantics. As an example, though, here's metadata tagging a slow test:
(fact "check Takeuchi's number" :slow ...)
Both lein-midje and the repl tools can be told to skip :slow
tests (or to run only slow tests). See using metadata to filter facts for more.
Sometimes you want to use a fact as a "todo". That can be written like this:
user=> (future-fact (time np) => polynomial?)
WORK TO DO at (NO_SOURCE_PATH:1)
If the fact has a description, that will also be printed.