Table of Contents
Assertions
All assertions record and continue — a failure does not end the test, so one
run tells you about every broken thing at once (Go's t.Error, JUnit's
assertAll). Only must stops the test.
| Form | Meaning |
|---|---|
(is form [message args...]) |
Record the result and continue. On failure, show the operands. |
(must form [message args...]) |
Like is, but abort the test (or subtest) on failure. |
(signals type form &key matching) |
form must signal a condition of type; returns that condition. |
(finishes form) |
form must return without an error; returns its value. |
(fail control args...) |
Record a failure unconditionally, with a formatted message. |
(skip &optional reason) |
End the test with status :skip. |
(note control args...) |
Attach a note, shown only if the test fails or errors. |
(abort-test) |
End the innermost test or subtest now. |
(approx= a b &key tolerance) |
` |
Message arguments are evaluated only on failure, so (is x "got ~a" (expensive)) costs nothing when it passes.
How is reads its form
is takes the macro environment and classifies its form:
- A function-call form —
(op arg...)whereopis a symbol that is neither a special operator nor a macro. Each non-literal argument is bound to a fresh variable and evaluated once, left to right, so evaluation count and order are exactly the source's. On failure every non-literal operand is shown with its value, andexplainis asked for a line. (not x)is classified by classifyingx, with the result negated. The shown operands arex's.- Anything else — an atom, a macro call, a special form, a lambda form — is opaque: it is evaluated once and the whole value is shown.
A literal is a constant atom, or a quote, function or lambda form.
Literals are evaluated in place and not shown. So in (is (= -1 (parse "-1")))
the -1 is not shown, but (parse "-1") is.
(let ((a 0))
(is (= (incf a) (incf a)))) ; a ends at 2; the operands are shown as 1 and 2
signals
type is an unevaluated type specifier. The form runs under handler-bind;
the first condition of type — any condition, including a warned warning
— ends it, and signals returns that condition so you can inspect it.
- If the form returns normally, the assertion fails: expected TYPE to be signaled, but the form returned normally.
- If an
errorof another type reaches the handler, the assertion fails (expected TYPE, got OTHER-TYPE: report) and returnsNIL; the error does not become a test error, and the next assertion still runs. - With
:matching "text", the assertion also fails when the condition's report does not contain the text.
(signals parse-error (parse "4x2"))
(signals error (/ 1 0) :matching "DIVISION-BY-ZERO")
(let ((c (signals my-error (do-thing))))
(is (= 42 (error-code c))))
finishes
Passes when the form returns normally, and returns its primary value. An
error fails the assertion (signaled TYPE: report) and the test continues; a
non-error condition is not caught.
(is (= 3 (finishes (+ 1 2))))
fail, skip, note, abort-test
failrecords a failed assertion whose message is the formatted text.skipsets the status to:skipwith the reason and ends the test; assertions recorded before it are kept but not shown.noteattaches text that appears undernotes:only when the test fails or errors — handy for logging the input that produced a failure.abort-testends the innermost test or subtest, and is safe to call from a helper function.
At the REPL
Outside any run, is, must, fail and note print what they would have
recorded, so you can try the framework without writing a test:
CL-USER> (is (= 1 1))
✓ (is (= 1 1))
CL-USER> (is (string= "abc" "abd"))
✗ (is (string= "abc" "abd"))
differ at index 2: "abc" vs "abd"
is and must return the predicate's primary value; skip and abort-test
do nothing and return NIL.
See Explain for the failure lines, and Output Format for the exact layout.