tl;dr

We demonstrate Clojure’s support for boiling down code to its very essence. Download the Leiningen sample setup github.com/JohannesFKnauf/regex-tdd-parameterized-clojure.test-example and start playing.

What else, if not JUnit or Spock?

We started with a blog article about testing regular expressions with JUnit 4 parameterized tests. We showed one improved variant for testing the exact same thing in a followup blog article about improving expressiveness and readability using Spock . In this last part of this series, we will demonstrate the same in Clojure.

The native build tool for Clojure is Leiningen. Clojure ships “batteries included” with a test library named clojure.test.

Running clojure.test using Leiningen

Install Leiningen and fire up a test run. In doubt, use the default way. Alternative mechanisms you might want to look at are

After installing we expect you to have a way of calling lein like this:

# lein --version
Leiningen 2.8.1 on Java 1.8.0_191 OpenJDK 64-Bit Server VM

Implementing the Tests in clojure.test

For JUnit 4 we needed special parameterized test annotations. Spock improved this by providing a special test syntax using compile-time AST transformations. In clojure.test we do not need any of this. We do not need special syntax. We do not need sample tables. We do not need frameworks or bigger libraries. We just express our intent – all the details hidden in plain sight.

The LISP way lets us focus on expressing the essence behind the tests in an almost natural language ({ insert random rant about vices and virtues of parentheses and prefix syntax here } resp. (random-rant (and vices virtues) (and parentheses prefix-syntax))). Some people tend to call it an embedded Domain Specific Language (DSL) and claim that the LISP community initiated the entire DSL philosophy a long time before it was hailed as DSL. But writing concise code in Clojure comes so natural, that it feels wrong to use such a magniloquent term.

Where do we end up?

(ns regex-test
  (:require [clojure.test :refer [deftest is]]))

(deftest regex-test
  (let [regex-under-test #"[-+]?\d+(\.\d+)?([eE][-+]?\d+)?"
        matching (partial re-matches regex-under-test)
        not-matching (comp not matching)]
    (do
      (is (not-matching "")             "empty string")
      (is (not-matching "a")            "single non-digit")
      (is (matching     "1")            "single digit")
      (is (matching     "123")          "integer")
      (is (matching     "-123")         "integer, negative sign")
      (is (matching     "+123")         "integer, positive sign")
      (is (matching     "123.12")       "float")
      (is (not-matching "123.12e")      "float with exponent extension but no value")
      (is (matching     "123.12e12")    "float with exponent")
      (is (matching     "123.12E12")    "float with uppercase exponent")
      (is (not-matching "123.12e12.12") "float with non-integer exponent")
      (is (matching     "123.12e+12")   "float with exponent, positive sign")
      (is (matching     "123.12e-12")   "float with exponent, negative sign"))))

Again, I want to point out the special beauties of this piece of code, as compared to the Spock and JUnit variants:

  • Instead of interpreting a true/false flag from the test tuples, we just define proper functions and use them directly in our test case descriptions. isMatchExpected = true becomes (is (matching ...) ...). isMatchExpected = false becomes (is (not-matching ...) ...).
  • Clojure’s let provides light-weight lexically scoped symbol definitions. We can keep our helper functions well-contained in our scope: 1 line each, no leaking.
  • The higher order functions partial and comp enable us to create a pointfree feeling when phrasing the test functions.
  • Like Groovy’s / ... / construct, Clojure provides syntax sugar for regular expressions with the #" ... " reader macro.

Running the tests

# lein test

lein test regex-test

Ran 1 tests containing 13 assertions.
0 failures, 0 errors.

No surprises. Nothing left to add. Have fun focussing on productivity!



Post header background image by Peter H from Pixabay.


Contact us