-
Notifications
You must be signed in to change notification settings - Fork 129
Background prerequisites
Background prerequisites are a way of describing default prerequisites so that you don't have to repeat them.
(fact
(against-background (f 1) => 2 ...) ; as in a #'provided clause
(function-that-may-call-f 2) => 33
....)
(against-background [(f 1) => 2 ...]
(fact ...) ; the background prerequisites optionally apply to each fact.
(fact ...)
....)
(background (f 1) => 2) ; applies to the rest of the file.
(fact ...)
Here's a silly little function to illustrate the idea:
(defn ready []
(and (pilot-ready) (copilot-ready) (flight-engineer-ready)))
Testing theory would tell us we need four tests: one that showed the case where the plane was ready, and three that showed cases where it wasn't. Each of the latter three would name a person who can override the decision to take off. Using Midje, the tests would look like this:
(facts
(ready) => truthy
(provided (pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => true)
(ready) => falsey
(provided (pilot-ready) => false, (copilot-ready) => true, (flight-engineer-ready) => true)
(ready) => falsey
(provided (pilot-ready) => true, (copilot-ready) => false, (flight-engineer-ready) => true)
(ready) => falsey
(provided (pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => false)))
That's pretty ugly, and all the detail obscures what's special about each fact. Even worse, the facts
don't check out:
FAIL for (t_sweet_test.clj:169)
You claimed the following was needed, but it was never used:
(copilot-ready)
FAIL for (t_sweet_test.clj:169)
You claimed the following was needed, but it was never used:
(flight-engineer-ready)
FAIL for (t_sweet_test.clj:172)
You claimed the following was needed, but it was never used:
(flight-engineer-ready)
Clojure's and
is short-circuiting, so it stops evaluating once it sees the first false value. That means the facts really have to be written like this:
(facts
(ready) => truthy
(provided (pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => true)
(ready) => falsey
(provided (pilot-ready) => false)
(ready) => falsey
(provided (pilot-ready) => true, (copilot-ready) => false)
(ready) => falsey
(provided (pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => false)))
Having tests know detail like the order of checks is The Wrong Thing (most of the time). Let's rethink.
For any given situation in real life, there's a sort of background of facts that we assume are true unless told otherwise. Because it's a pool of facts that apply in many situations, we're not alarmed if a particular one doesn't apply in some particular situation. That given, here's how you can abbreviate the facts about ready
:
(facts
(ready) => truthy
(ready) => falsey (provided (pilot-ready) => false)
(ready) => falsey (provided (copilot-ready) => false)
(ready) => falsey (provided (flight-engineer-ready) => false)
(against-background
(pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => true))
The above says "We're normally ready. Here are exceptions that make us unready. Here, by the way, is the background against which exceptions operate."
You can place the against-background
statement anywhere you think is clearest. I tend to put it last.
If you have backgrounds that apply to many facts
, you can use a wrapper macro. (Note the square brackets.)
(against-background [(pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => true]
(fact (ready) => truthy)
(fact "exceptions"
(ready) => falsey (provided (pilot-ready) => false)
(ready) => falsey (provided (copilot-ready) => false)
(ready) => falsey (provided (flight-engineer-ready) => false)))
You can also set a background that applies to the rest of the file:
(background
(pilot-ready) => true, (copilot-ready) => true, (flight-engineer-ready) => true)
(fact ...)
If you use deftest
to wrap facts, define the background within the deftest
.