Background prerequisites

Reynald Borer edited this page Jan 9, 2018 · 17 revisions

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.

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.