Testing And Benchmarking in Clojure

Here’s the translation of the Go testing and benchmarking example to Clojure:

Unit testing is an important part of writing principled Clojure programs. The clojure.test namespace provides the tools we need to write unit tests, and we can use the Leiningen build tool to run tests.

For the sake of demonstration, this code is in the user namespace, but it could be any namespace. Testing code typically lives in a separate namespace with a -test suffix.

(ns user
  (:require [clojure.test :refer :all]))

;; We'll be testing this simple implementation of an
;; integer minimum. Typically, the code we're testing
;; would be in a source file named something like
;; `src/myproject/core.clj`, and the test file for it would then
;; be named `test/myproject/core_test.clj`.

(defn int-min [a b]
  (if (< a b)
    a
    b))

;; A test is created by writing a function with a name
;; beginning with `test-`.

(deftest test-int-min-basic
  (let [ans (int-min 2 -2)]
    (is (= ans -2) "int-min should return the minimum of two numbers")))

;; Writing tests can be repetitive, so it's idiomatic to
;; use a data-driven style, where test inputs and
;; expected outputs are listed in a collection and a single
;; test function walks over them and performs the test logic.

(deftest test-int-min-data-driven
  (let [tests [[0 1 0]
               [1 0 0]
               [2 -2 -2]
               [0 -1 -1]
               [-1 0 -1]]]
    (doseq [[a b expected] tests]
      (testing (str a "," b)
        (is (= (int-min a b) expected))))))

;; Benchmark tests in Clojure are typically done using external
;; libraries like Criterium. Here's a simple example using
;; Criterium's `quick-bench` function.

(defn benchmark-int-min []
  (require '[criterium.core :as c])
  (c/quick-bench (int-min 1 2)))

To run all tests in the current project:

$ lein test

Running tests in user
user=> (run-tests)

Testing user
...
Ran 2 tests containing 6 assertions.
0 failures, 0 errors.
{:test 2, :pass 6, :fail 0, :error 0, :type :summary}

To run the benchmark (assuming you have Criterium in your project dependencies):

$ lein repl
user=> (benchmark-int-min)
Evaluation count : 39741990 in 6 samples of 6623665 calls.
             Execution time mean : 15.096758 ns
    Execution time std-deviation : 0.252222 ns
   Execution time lower quantile : 14.850116 ns ( 2.5%)
   Execution time upper quantile : 15.428523 ns (97.5%)
                   Overhead used : 1.943557 ns

In Clojure, we don’t typically separate benchmarks into a different file or use special naming conventions for them. Instead, we often include benchmarking functions in the same namespace as the tests or the actual code, and run them explicitly when needed.

The clojure.test framework provides a rich set of assertion macros and test organization tools. The testing macro allows for nested contexts within tests, which can be particularly useful for data-driven tests.

For more complex testing scenarios, many Clojure developers use additional testing libraries like test.check for property-based testing or matcher-combinators for more expressive assertions.