Writing Unit Tests with clojure.testλ︎
Unit tests are centered on assertions, testing if something returns a true or false value.
is
function is the simplest assertion and the most common. It checks to see if an expression given is true and if so then the assertion passes. If the value is false then that assertion fails.
as
provides a way to run the same assertion with different values, testing the same function with a collection of arguments. This provides a clean way to test a function without lots of repetition.
testing
is a macro to group multiple assertions together, providing a string in which to describe the context the assertions are testing. The well worded context string is invaluable for narrowing down on which assertions are failing.
deftest
is a collection of assertions, with or without testing
expressions. The name of the deftest should be the name of the function it is testing with -test
as a postfix. For example, the function practicalli.playground/calculator
would have a deftest
called practicalli.playground-test/calculator-test
Requiring Namespacesλ︎
A test namespace has a singular purpose to test a matching src namespace.
The idiomatic approach is to :refer
specific functions from clojure.test
as those functions are used.
The namespace to be tested is referred using a meaningful alias. The alias highlight the exact functions being tested in the body of the code. This provides a visual way to separate functions under test with other test functions, especially if there are helper functions or vars used for test data.
Simple Exampleλ︎
(deftest public-function-in-namespace-test
(testing "A description of the test"
(is (= 1 (public-function arg)))
(is (predicate-function? arg))))
Assertion data setλ︎
The are
macro can also be used to define assertions, especially when there would otherwise be multiple assertions that only differ by their test data.
An are
assertion defines the arguments to the test, the logic of the test and a series of test data.
This is equivalent to writing
Refactor test assertion to use data set
Assertions in the test take the same shape of values, so are candidates to refactor to the are
macro.
(deftest encoder-test
(testing "Tens to number words"
(is (= '("zero" "ten")
(character-sequence->word-sequence dictionary/digit->word '(\0 \1 \0))))
(is (= '("zero" "eleven")
(character-sequence->word-sequence dictionary/digit->word '(\0 \1 \1))))
(is (= '("zero" "twenty" "zero")
(character-sequence->word-sequence dictionary/digit->word '(\0 \2 \0))))
(is (= '("zero" "twenty""one")
(character-sequence->word-sequence dictionary/digit->word '(\0 \2 \1))))
(is (= '("zero" "forty" "two")
(character-sequence->word-sequence dictionary/digit->word '(\0 \4 \2))))))
(deftest encoder-test
(testing "Tens to number words"
(are [words numbers]
(= words (character-sequence->word-sequence dictionary/digit->word numbers))
'("zero" "ten") '(\0 \1 \0)
'("zero" "eleven") '(\0 \1 \1)
'("zero" "twenty" "zero") '(\0 \2 \0)
'("zero" "twenty""one") '(\0 \2 \1)
'("zero" "forty" "two") '(\0 \4 \2)))
Generative Testing provides a wide range of values
Generating test data from Clojure Specs provides an extensive set of values that provide an effective way to test functions.
Referenceλ︎
Code challenges with testsλ︎
TDD Kata: Recent Song-list TDD Kata: Numbers in words Codewars: Rock Paper Scissors (lizard spock) solution practicalli/codewars-guides practicalli/exercism-clojure-guides