-
Notifications
You must be signed in to change notification settings - Fork 0
A tutorial introduction
Add this to your .lein/profiles.clj
file:
{:user {:plugins [[lein-midje "3.0-beta1"]]}}
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:
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.5-beta1"]]}}) ;; <<==
For historical reasons, the main namespace for Midje is oddly named:
(ns quux.t-core
(:use midje.sweet) ;; <<==
(:require [quux.core :as core]))
Midje's core construct is a fact. In an idealistic sense, a fact represents a claim about some world, typically the world of a program. Here's a claim about Clojure's implementation of arithmetic (and, as it happens, any correct implementation of arithmetic):
(fact "There exist numbers such that (+ n n) is equal to (* n n).")
Midje can't actually do anything with that claim, since (1) it can't read English, and (2) even if it could, it has no mechanism for searching through numbers until it finds a confirming example. Instead, you have to provide the example. Here are three of them. Two are good (true) examples, and one is bad (false):
(fact "There exist numbers such that (+ n n) is equal to (* n n)."
(+ 0 0) => (* 0 0)
(+ 2 2) => (* 2 2)
(+ 4 4) => (* 4 4))
Read those as "zero plus zero is zero times zero", etc. Midje's syntax follows that used in most books that provide examples of what code does: there's the code in question, then some delimiter (most often some kind of arrow or a newline), then what you'd expect to see if you typed that code to a repl.
Midje uses the term prediction for such arrow-containing syntactic forms. I don't use "example", because in real life, good examples might contain more than one arrow form. I like "prediction" because it implies that, at some point, the prediction is either going to come true or not come true. For Midje predictions, that point is when Midje is run.
Let's use lein-midje
to check the three predictions:
(I'll be using screen shots to show how Midje uses terminal colors. You can turn colorization off if you like.)
The third prediction failed. We'll fix that, but let me first show you how to avoid the slow startup due to having to load the JVM, Clojure, and Midje for each lein midje
.
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.
Do this:
I'll let you explore Midje's in-repl documentation on your own. For now, type the following:
Notice that you still have the repl prompt, so you can combine autotesting with interactive repl development. For example, you can double-check results:
If you now delete the incorrect claim from t_core.clj
, you'll see this when you save the file:
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 =
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 prediction, 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 predictions. 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 predictions 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 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
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|http://www.growing-object-oriented-software.com]], 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.
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.