Friday, July 11, 2014

A suggestion for testing style in Clojure

Getting a test to fail is one thing; getting it to express why it failed is another.
Clojure.test provides an assertion macro: is

(deftest "my-function" 
  (testing "some requirement"
    (is (something-that-evaluates-to-bool (arg1) (arg2)) 
    "Message to print when it's false")))

When this assertion fails, in addition to the message, "expected" and "actual" results print. The is macro tries to be a little clever.

If the expression passed to is an S-expr, and the first element of the is recognized as a function. Then is prints that first symbol directly, then evaluates all the arguments to the function and prints the results. For instance:

expected: (function-name (arg1) (arg2))
actual: (not (function-name "1st arg value" "2nd arg value"))

However, if is does not recognize that first element as a function, the whole expression passed to is is evaluated for the actual, and you get:

expected: (something-that-evaluates-to-bool (arg1) (arg2))
actual: false

To get the more communicative output, write a function with a name that describes the property you're testing. A top-level defn will be recognized by is as a function, so declare your test function that way.

For instance, if I'm testing whether a vector of numbers is sorted, I could use built-in functions:

(let [v [1 3 2 4]]
(is (apply <= v) "not sorted"))

Then I see[1]:

expected: (apply <= v)
actual: (not
           (apply
            #<core$_LT__EQ_ clojure.core$_LT__EQ_@50055df8>
            [1 3 2 4]))

Printing a function is never pretty. If, instead, I give the desired property a name:

(defn ascending? [v] (apply <= v))

(let [v [1 3 2 4]]
  (is (ascending? v) "not sorted"))

Then I get something much more communicative:

expected: (ascending? v)
actual: (not (ascending? [1 3 2 4]))

There it is -- declarative test output through creation of simple functions.

Bonus: If you want to make your failure output even better, define special functionality just for your function, as @pjstadig did for =.

-----------------
[1] I'm using humane-test-output to get the "actual" pretty-printed.

Clojure

No comments:

Post a Comment