A Hierarchy of Tests
Now that you’ve established test functions as first-class citizens, the question might arise, should test-arithmetic
be a test function? As things stand, it doesn’t really matter—if you did define it with deftest
, its binding of *test-name*
would be shadowed by the bindings in test-+
and test-*
before any results are reported.
But now imagine you’ve got thousands of test cases to organize. The first level of organization is provided by test functions such as test-+
and test-*
that directly call check
. But with thousands of test cases, you’ll likely need other levels of organization. Functions such as test-arithmetic
can group related test functions into test suites. Now suppose some low-level test functions are called from multiple test suites. It’s not unheard of for a test case to pass in one context but fail in another. If that happens, you’ll probably want to know more than just what low-level test function contains the test case.
If you define the test suite functions such as test-arithmetic
with deftest
and make a small change to the *test-name*
bookkeeping, you can have results reported with a “fully qualified” path to the test case, something like this:
pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)
Because you’ve already abstracted the process of defining a test function, you can change the bookkeeping details without modifying the code of the test functions.6 To make *test-name*
hold a list of test function names instead of just the name of the most recently entered test function, you just need to change this binding form:
(let ((*test-name* ',name))
to the following:
(let ((*test-name* (append *test-name* (list ',name))))
Since **APPEND**
returns a new list made up of the elements of its arguments, this version will bind *test-name*
to a list containing the old contents of *test-name*
with the new name tacked onto the end.7 When each test function returns, the old value of *test-name*
will be restored.
Now you can redefine test-arithmetic
with deftest
instead of **DEFUN**
.
(deftest test-arithmetic ()
(combine-results
(test-+)
(test-*)))
The results now show exactly how you got to each test expression.
CL-USER> (test-arithmetic)
pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)
pass ... (TEST-ARITHMETIC TEST-+): (= (+ 1 2 3) 6)
pass ... (TEST-ARITHMETIC TEST-+): (= (+ -1 -3) -4)
pass ... (TEST-ARITHMETIC TEST-*): (= (* 2 2) 4)
pass ... (TEST-ARITHMETIC TEST-*): (= (* 3 5) 15)
T
As your test suite grows, you can add new layers of test functions; as long as they’re defined with deftest
, the results will be reported correctly. For instance, the following:
(deftest test-math ()
(test-arithmetic))
would generate these results:
CL-USER> (test-math)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2 3) 6)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ -1 -3) -4)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-*): (= (* 2 2) 4)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-*): (= (* 3 5) 15)
T