Background prerequisites

conan edited this page Apr 24, 2014 · 16 revisions
Clone this wiki locally

Background prerequisites are a way of describing default prerequisites so that you don't have to repeat them.

##The short version##

     (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 ...)

##Details##

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 ...)

##clojure.test##

If you use deftest to wrap facts, define the background within the deftest.