Skip to content

A tutorial introduction

Casey Link edited this page Dec 21, 2017 · 34 revisions

Add this to your ~/.lein/profiles.clj file:

{:user {:plugins [[lein-midje "3.2.1"]]}}

Then find the temporary directory of your choice and type this:

% lein new midje quux

Lein-midje will produce a conventional directory structure containing sample test and source files:

.
├── project.clj
├── README.md
├── src
│   └── quux
│       └── core.clj
└── test
    └── quux
        └── core_test.clj

Midje lets you intermingle tests and source in the same file, but we'll stick with the traditional separation in this tutorial.

The project.clj file contains Midje itself as a development dependency:

(defproject quux "0.0.1-SNAPSHOT"
  :description "Cool new project to do things and stuff"
  :dependencies [[org.clojure/clojure "1.4.0"]]
  :profiles {:dev {:dependencies [[midje "1.6.3"]]}})  ;; <<==

For historical reasons, the main namespace for Midje is oddly named:

(ns quux.core-test
  (:use midje.sweet)               ;; <<==
  (:require [quux.core :as core]))

Checkables

Suppose you were writing a book on Clojure and you wanted to explain conj. You might write some text like "conj appends its element to the end of a vector but to the front of a list or lazy sequence". And then you might have three examples, perhaps written like this:

(conj [1 2] 3)           ;; [1 2 3]
(conj '(1 2) 3)          ;; (3 1 2)
(conj (map inc [0 1]) 3) ;; (3 1 2)

Or like this (the style of my own Functional Programming for the Object-Oriented Programmer):

user=> (conj [1 2] 3)
[1 2 3]
user=> (conj '(1 2) 3)
(3 1 2)
user=> (conj (map inc [0 1]) 3)
(3 1 2)

Or like this (the style of The Joy of Clojure):

(conj [1 2] 3)
;=> [1 2 3]
(conj '(1 2) 3)
;=> (3 1 2)
(conj (map inc [0 1]) 3)
;=> (3 1 2)

Arrows are popular in examples (see also). That makes sense. First of all, people who grew up with languages written left to right and top to bottom typically think of time flowing the same way. (Look at your English-language calendar.) So it's intuitive to think of the conj expression on the left: it's evaluated first, then you see the result. The arrows are also natural, since they're both associated with the movement of time and used to point out something interesting that happened. At the very least, arrows serve to visually separate the two forms of interest.

Midje follows that tradition by using example-like syntax in its checkables:

    ;; the following pseudo-expressions are checkables
    (conj [1 2] 3) => [1 2 3]
    (conj '(1 2) 3) => '(3 1 2)
    (conj (map inc [0 1]) 3) => '(3 1 2)

Many people find the non-Lispy syntax bizarre. Midje used to offer an alternate, more conventional syntax:

    (expect (conj [1 2] 3) => [1 2 3])
    (expect (conj '(1 2) 3) => '(3 1 2))
    (expect (conj (map inc [0 1]) 3) => '(3 1 2))

(The arrow is semantically relevant: there are other kinds of arrows.)

However, expect has rarely been used (and is now deprecated), so it seems Midje users get used to the oddity.

Facts

Checkables can't be checked in isolation. The smallest checkable unit in Midje is the fact. That name is a gesture toward what we really want: to be able to make claims about our code that are as strong as mathematical facts. We want to make lofty statements like "for all strings s and all regular expressions re, it is a fact that (str/split s re) is ...".

And indeed, we do make such claims. But we can't back them up, not like mathematicians can. The best we can do in normal programming is to claim the general fact and provide enough specific examples that we have strong confidence that the general claim is true. That can look like this:

(fact "`split` splits strings on regular expressions and returns a vector"
  (str/split "a/b/c" #"/") => ["a" "b" "c"]
  (str/split "" #"irrelevant") => [""]
  (str/split "no regexp matches" #"a+\s+[ab]") => ["no regexp matches"])

It's Midje's job to check the checkables (to try to confirm the examples) and report helpfully when they fail.

Let's be realistic

I personally try to think of checkables as examples of more general facts. Other people like to think of them as the facts themselves. Those people tend to use facts instead of fact:

(facts "about `split`"
  (str/split "a/b/c" #"/") => ["a" "b" "c"]
  (str/split "" #"irrelvant") => [""]
  (str/split "no regexp matches" #"a+\s+[ab]") => ["no regexp matches"])

Other people think all this talk about facts is nonsense and treat fact as nothing more than a way of grouping checkables and nested facts, similar to context in Ruby test frameworks. Midje is agnostic about how you think about its names. If you want to treat "fact" as a misspelling of "test", go ahead.

Checking facts

You won't be testing clojure.string/split in your normal work. So let's consider a simple function that contains a bug.

The scenario is this: a programmer comes to Clojure. She wants to write a function that returns the first element of a sequence. If the sequence is empty, she wants it to return some default value.

(defn first-element [sequence default]
  (if (nil? sequence)
    default
    (first sequence)))

The function is buggy because Clojure is not like traditional Lisps in which nil is a synonym for () and so she's become used to using it where a Clojure programmer would use empty?.

However, she's a careful programmer, so she also decided to try her function against various kinds of "emptiness" cases as well as a couple of "happy path" cases:

(facts "about `first-element`"
  (fact "it normally returns the first element"
    (first-element [1 2 3] :default) => 1
    (first-element '(1 2 3) :default) => 1)

  ;; I'm a little unsure how Clojure types map onto the Lisp I'm used to.
  (fact "default value is returned for empty sequences"
    (first-element [] :default) => :default
    (first-element '() :default) => :default
    (first-element nil :default) => :default
    (first-element (filter even? [1 3 5]) :default) => :default))

Notice that she's used nested facts to indicate the difference between the two groups of checkables. She is rewarded for her care, because some checks fail:

A lein-midje failure

(I'll be using screen shots to show how Midje uses terminal colors. You can turn colorizing off if you like.)

Before fixing that, let me show you how to avoid the slow startup due to having to load the JVM, Clojure, and Midje for each lein midje.

Autotest

Many testing tools provide autotest. An autotest tool starts with one test run that checks everything. But instead of exiting, the tool then watches the source and test directories for changes. When it sees one, it reloads both the changed file and any files that depend on it, either directly or indirectly. (In Midje's case, the dependencies are those documented in each file's ns expression.)

Lein-midje can start up autotesting from the command line:

690 $ lein midje :autotest

However, I usually find it more useful to use the repl tools.

Autotesting in the repl

Do this:

Using the repl tools

I'll let you explore Midje's in-repl documentation on your own. For now, type the following:

Autotest in the repl

Notice that you still have the repl prompt, so you can combine autotesting with interactive repl development. For example, you can double-check results:

Using the repl tools

Go ahead and fix the source. Here's what happened as I did, typo and all.

Using the repl tools

Extended equality and checkers

Midje doesn't use = to compare the left-hand to right-hand sides, but rather its own extended equality. There are various small – and two large – differences between equality and extended equality.

The first of the large differences is the handling of regular expressions. Equality is a pretty useless concept for them, since regular expressions aren't even equal to non-identical copies:

user=> (= #"a.b" #"a.b")
false

In a Midje checkable, a regular expression on the right-hand side causes a check for a partial match:

user=> (fact "O wad    some pow'r" => #"wad\s+some")
true

That is, the checking is done with re-find.

The larger of the two large differences is the handling of functions on the right-hand side. Under extended equality, the actual result isn't compared to the function. Instead it is passed to the function and the result is checked for "truthiness" (that is, any value other than nil or false). Here's a simple example:

user=> (fact (+ 1 2) => even?)

FAIL at (NO_SOURCE_PATH:1)
Actual result did not agree with the checking function.
        Actual result: 3
    Checking function: even?
false

Midje comes prepackaged with useful functions, called checkers, for your checkables. The two most used are probably truthy and falsey. Consider this expression:

(some even? [1 5])

The result of that expression is nil, so we could write this fact:

(fact
  (some even? [1 5]) => nil)

That works, but it does a poor job of expressing intent. Most often, we don't care specifically that the result is nil — we care that the result counts as false. So this is better:

(fact
  (some even? [1 5]) => falsey)

Note: you can also negate the arrow to force a check for inequality:

(fact
  (some even? [1 5]) =not=> truthy)

One disadvantage of truthy and falsey is that checkables using them often do not read clearly because they don't match the way we normally speak or write. Consider [1 3 5 8]. Most people would be more likely to say "The array has an [or "some"] even element" than "A search for some even element would yield a truthy value." We can use extended equality to match the way people speak:

(fact
  [1 3 5 8] => #(some even? %)   ; or...
  [1 3 5 8] => (partial some even?))

Unless you're a huge fan of higher-order functions and can grasp their meaning immediately, that looks kind of awkward. Midje provides a checker that looks nicer:

(fact
  [1 3 5 8] => (contains even?))

This works because contains interprets its arguments using extended equality (so that functions are given actual results as arguments). However, it and other collection checkers are probably most often used with non-function values. Here are two examples. I've made them fail so you can see what failure output looks like:

user=> (fact {:a 1, :b 2} => (just {:a 1, :CCC 333}))

FAIL at (NO_SOURCE_PATH:1)
Actual result did not agree with the checking function.
        Actual result: {:a 1, :b 2}
    Checking function: (just {:CCC 333, :a 1})
    The checker said this about the reason:
        Best match found: {:a 1}
false
user=> (fact [1 2 3 4 5] => (contains [1 2 4]))

FAIL at (NO_SOURCE_PATH:1)
Actual result did not agree with the checking function.
        Actual result: [1 2 3 4 5]
    Checking function: (contains [1 2 4])
    The checker said this about the reason:
        Best match found: [1 2]
false

The second example fails because contains by default looks for a contiguous subsequence. That can be overridden:

user=> (fact [1 2 3 4 5] => (contains [1 2 4] :gaps-ok))
true

Top-down development and the logical structure of programs

Test-driven design can be done either bottom up (in a way reminiscent of traditional Lisp repl-driven development) or top down (as described in Growing Object-Oriented Software, Guided by Tests, one of the strong early inspirations for Midje). Since there are various paths through this user documentation, I'll point you to this introduction if you're interested in learning about how Midje views the top-down approach in a functional language.

In summary

With Midje, I've aimed to support bottom-up design, top-down design, and (most importantly) a smooth alternation between the two. I've also aimed to combine the ease of repl-based development with the long-term value of putting tests in files. To judge how well I've met my goals, you'll have to put Midje to use.

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.