How Midje looks at the world

Trevor Wennblom edited this page Oct 23, 2013 · 3 revisions
Clone this wiki locally

Midje tries to be both idealistic and practical.

Yay, idealism!

Midje's idealistic view of the world revolves around facts. Here's a fact:

(fact
  (str 1 2) => "12")

That's true of any correct implementation of str. It would be bad if (str 1 2) were not "12".

However, the connection between that Clojure form and the common English word "fact" is unclear. What general-purpose fact does that particular expression really tell us about the str function?

So let's look at another example, a fact about integers:

(fact "for some numbers, adding it to itself is the same as multiplying it by itself")

That's a more definitive and more sweeping statement. However, a person encountering it might ask two questions:

  • "For example? (Your abstract statement isn't wildly useful.)"
  • "Are you sure?"

Those questions can be answered like this:

(fact "for some numbers, adding it to itself is the same as multiplying it by itself"
  (+ 0 0) => (* 0 0)
  (+ 2 2) => (* 2 2))

Here, we've shown two numbers (0 and 2) for which the fact is true. Moreover, a dumb program (Midje) can check that the claim is really true for those two numbers. If it's not true for one of them, we know we need to do some work on the implementation of addition or multiplication (or question the very foundations of all our beliefs -- but Midje will leave such existential crises to you).

Here's another factual claim:

(fact "addition is the inverse of subtraction"
  (-> 5 (+ 10) (- 10)) => 5))

This example is slightly different. If Midje checks the -> form and approves of it, we don't absolutely know that the claimed fact is true for all numbers: we really only know it's true of 5 and 10. But -- provided we chose a good example -- our confidence that the fact is true is sufficiently heightened that we can move on to other problems.


Generally speaking, functions don't stand alone. A function we write will use other functions. In such a case, a claim that "it is a fact that (f 1) produces..." might only be true when a subsidiary claim about g (which f uses) is also true. As a simple example, consider these two interrelated functions:

(defn g [n] n)
(defn f [n] n)

Here's a fact about f:

(fact "f returns its argument"
  (f 1) => 1)

While that's true, we might want to document the relationship between f and g:

(fact "f returns whatever g returns"
  (f 1) => 1
  (provided 
    (g 1) => 1))

An interesting thing about the previous example is that the fact doesn't depend on the argument 1. 1 is just some random constant that we we picked only because we have to pick something. (Neither Midje nor any other program could check the claimed fact without some specific value.)

A reader might find the fact clearer if it were written like this:

(fact "f returns whatever g returns"
  (f ..whatever..) => ..result..
  (provided 
    (g ..whatever..) => ..result..))

Midje encourages you to use that style of description whenever it clarifies. Such ..whatever.. forms are a Midje innovation. They're called metaconstants. They say "nothing about the value matters except what is specifically stated in the fact." (You'll see later how you can state things about metaconstants.) That adds clarity because if you see a constant like 1 you might wonder "Is there something special about that value? Does f somehow depend on the fact that (* n 1) is always n?" With a metaconstant, you know that anything that matters about the argument will be explicitly stated. You don't have to worry whether f only appears to work because of some unanticipated happenstance of the specific argument used in the example.

Yay, practicality!

Although it's entertaining and (I believe) enlightening to think of programs as a graph of functions connected in a dependency relationship, with factual claims decorating the nodes and arcs, it's a fact that programs are code, written by people, over time. A testing tool that doesn't accommodate that reality is idle wankery. Therefore:

  • Midje facts are also a lexical scoping mechanism that you can use much like you use let and letfn in code: to clarify execution and remove duplication.
  • You can pile a series of actual => expected checks into a fact without having to claim they add up to some Grand Truth.
  • While simple facts like this:

    (fact "for some numbers, adding it to itself is the same as multiplying it by itself"
      (+ 0 0) => (* 0 0)
      (+ 2 2) => (* 2 2))

    ... look very nice, realistic tests are often more complicated. Midje allows such actual => expected expressions to be arbitrarily embedded in setup code.

  • Both the language of Midje and the tools it provides support the two common styles of coding: the one where you develop small functions (often working in the repl) and compose them into larger ones, and the one where you "program by wishful thinking" and write high-level functions that use simpler functions you pretend must exist to do specific subtasks. Only when you're happy with a higher-level function do you begin to work on the (supposedly) simpler ones. In particular:

    • Midje supports top-down development, making it easy for functions-under-development to verifiably use functions that don't exist yet.
    • Midje has a number of tools that support working in a fast-turnaround "repl-driven" way, but with the added benefit that confirmation work is easily preserved as a permanent test suite.