-
Notifications
You must be signed in to change notification settings - Fork 128
Tabular facts
Experimental feature: available with 1.2-alpha1
Sometimes it's convenient to illustrate a fact with a table of values. For example, Conway's Life has rules for when cells are born, stay alive, or die. They look nice expressed as a table:
(tabular
(fact "The rules of Conway's life"
(alive? ?cell-status ?neighbor-count) => ?expected)
?cell-status ?neighbor-count ?expected
:alive 1 FALSEY ; underpopulation
:alive 2 truthy
:alive 3 truthy
:alive 4 FALSEY ; overpopulation
;; A newborn cell has three parents
:dead 2 FALSEY
:dead 3 truthy
:dead 4 FALSEY)
Note that you can capitalize falsey
. FALSEY
and TRUTHY
are synonyms for the lowercase form. I find that using the capitalized form for one makes a tabular fact easier to read.
In case of a failure, the line number points to the check (the arrow form), as usual. But it's augmented by a description of which set of values caused the failure. For example, if alive?
always returns false, the fact above will yield three failures:
FAIL at (t_sweet.clj:237)
With table substitutions: {?cell-status :alive, ?neighbor-count 2, ?expected truthy}
Actual result did not agree with the checking function.
Actual result: false
Checking function: truthy
FAIL at (t_sweet.clj:237)
With table substitutions: {?cell-status :alive, ?neighbor-count 3, ?expected truthy}
Actual result did not agree with the checking function.
Actual result: false
Checking function: truthy
FAIL at (t_sweet.clj:237)
With table substitutions: {?cell-status :dead, ?neighbor-count 3, ?expected truthy}
Actual result did not agree with the checking function.
Actual result: false
Checking function: truthy
In a nod to Groovy's Spock test framework, vertical bars are treated as white space (like commas are throughout Clojure) and :where
can be used to separate the fact from the table of substitutions:
(tabular
(fact
(inc ?int) => ?expected)
:where
| ?int | ?expected
| 1 | 2
| 2 | 4 )
Substitutions work with more than just plain values. For example, instead of using truthy
and falsey
, you can substitute the checking arrow, or you can substitute a calculation for a value:
(tabular
(fact "only two numbers have the same sum and square"
(* ?n ?n) ?arrow (+ ?n ?n))
?n ?arrow
0 =>
2 =>
;; Failure cases
1 =not=>
(* 10 10) =not=> ; note calculation
;; and so on
)
Tabular facts behave exactly like regular facts. They can be turned into future facts:
(tabular
(future-fact (inc ?int) => ?int)
?int
1)
They obey background prerequisites, setup, teardown, and state, and so on.
You can have many checks within the fact
. (That is, you're not limited to a single arrow.)
As of 1.2-alpha5, tabular
can take a doc string:
(tabular "increment works"
(fact (inc ?int) => ?int)
?int
1)
This is convenient when using M-x midje-hide-facts
in Midje mode.
tabular
is a macro whose arguments should look like this:
(tabular target-form & headers+values)
The header consists of 0 or more ?-variables. The remaining values must be a whole-number multiple of the number of ?-variables; you can consider them divided into rows.
tabular
expands number-of-rows copies of its target-form, each with the ?-variables substituted with their corresponding row values. A ?variable can appear more than once in the target-form. It's not an error if a ?-variable doesn't appear in the target-form.