1 Structure
dxtr edited this page 2026-10-18 12:07:40 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Structure

assay has no suites and no fixture registry. You get two building blocks — subtest and cases — and :wrap for setup and teardown.

subtest

subtest is Go's t.Run: a named child with its own result, its own assertions and its own abort boundary.

(subtest name body...)   ; NAME is evaluated; a string, symbol or number

An unhandled error or a must failure inside a subtest ends the subtest only; the test continues after it. A failing or erroring child makes the parent fail; a skipped child does not. Subtests nest, and their names join with › in the report.

(deftest roundtrip
  (dolist (s '("0" "1" "-7" "1000000"))
    (subtest s
      (is (string= s (unparse (parse s)))))))

A failure in one iteration is reported under roundtrip › -7 and the loop keeps going.

cases

cases is the table-driven idiom on top of subtest: one subtest per row.

(cases (var...) rows body...)   ; ROWS evaluates to a list of lists

vars is a lambda list used with destructuring-bind on each row, so a row may be shorter than the list with &optional. Each subtest is named from the variables and their values, var=value pairs separated by one space, the values printed with prin1.

(deftest arithmetic
  (cases (expr expected)
      '(("1+1" 2)
        ("2*3" 6)
        ("7/2" 7/2))
    (is (= expected (eval-string expr)))))

The failing row above is reported as:

✗ parser/test/arithmetic › expr="7/2" expected=7/2
    (is (= expected (eval-string expr)))
      expected           ⇒ 7/2
      (eval-string expr) ⇒ 3
      differ by 1/2

Fixtures

There is no fixture mechanism because there does not need to be one: a with- macro plus unwind-protect is a fixture, and deftest's :wrap applies one to a whole body.

(defmacro with-temp-dir ((var) &body body)
  `(let ((,var (make-temp-dir)))
     (unwind-protect (progn ,@body)
       (delete-directory ,var))))

(deftest (writes-a-file :wrap ((with-temp-dir (dir))))
  (write-report (merge-pathnames "r.txt" dir))
  (is (probe-file (merge-pathnames "r.txt" dir))))

For per-assertion setup inside a body, just use let and with- directly. There are no @BeforeAll-style hooks, and no ordering guarantees between tests beyond definition order in a serial run.

Next: Running.