lisp-unit is a Common Lisp library that supports unit testing. It is an extension of the library written by Chris Riesbeck . There is a long history of testing packages in Lisp, usually called "regression" testers. More recent packages in Lisp and other languages have been inspired by JUnit for Java. For more information on both unit testing and JUnit, visit www.junit.org.

This page has three parts:

Overview

The main goal for lisp-unit was to make it simple to use. The advantages of lisp-unit are:

How to Use lisp-unit

  1. Load (or compile and load) (asdf:operate 'asdf:load-op :lisp-unit).
  2. Evaluate (in-package :lisp-unit).
  3. Load a file of tests. See below for how to define tests.
  4. Run the tests with run-tests.

Any test failures will be printed, along with a summary of how many tests were run, how many passed, and how many failed.

You define a test with define-test:

(define-test name exp1 exp2 ...)

This defines a test called name. The expressions can be anything, but typically most will be assertion forms.

Tests can be defined before the code they test, even if they're testing macros. This is to support test-first programming.

After defining your tests and the code they test, run the tests with

(run-tests)

This runs every test defined in the current package. To run just certain specific tests, use

(run-tests name1 name2 ...)

e.g., (run-tests greater summit).

The following example

First, we define some tests.

> (in-package :example)
#<PACKAGE EXAMPLE>
> (define-test test-my-max
   (assert-equal 5 (my-max 2 5))
   (assert-equal 5 (my-max 5 2))
   (assert-equal 10 (my-max 10 10))
   (assert-equal 0 (my-max -5 0)))
TEST-MY-MAX

Following good test-first programming practice, we run these tests before writing any code.

> (run-tests test-my-max)
TEST-MY-MAX: Undefined function MY-MAX called with arguments (2 5).

This shows that we need to do some work. So we define our broken version of my-max.

> (defun my-max (x y) x)  ;; deliberately wrong
MY-MAX

Now we run the tests again:

> (run-tests my-max)
MY-MAX: (MY-MAX 2 5) failed: Expected 5 but saw 2
MY-MAX: (MY-MAX -5 0) failed: Expected 0 but saw -5
MY-MAX: 2 assertions passed, 2 failed.

This shows two failures. In both cases, the equality test returned NIL. In the first case it was because (my-max 2 5) returned 2 when 5 was expected, and in the second case, it was because (my-max -5 0) returned -5 when 0 was expected.

Assertion Forms

The most commonly used assertion form is

(assert-equal value form)

This tallies a failure if form returns a value not equal to value. Both value and test are evaluated in the local lexical environment. This means that you can use local variables in tests. In particular, you can write loops that run many tests at once:

> (define-test my-sqrt
  (dotimes (i 5)
    (assert-equal i (my-sqrt (* i i)))))
MY-SQRT

> (defun my-sqrt (n) (/ n 2))   ;; wrong!!

> (run-tests my-sqrt)
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 1 but saw 1/2
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 3 but saw 9/2
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 4 but saw 8
MY-SQRT: 2 assertions passed, 3 failed.

However, the above output doesn't tell us for which values of i the code failed. Fortunately, you can fix this by adding expressions at the end of the assert-equal. These expression and their values will be printed on failure.

> (define-test my-sqrt
  (dotimes (i 5)
    (assert-equal i (my-sqrt (* i i)) i)))  ;; added i at the end
MY-SQRT
> (run-tests my-sqrt)
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 1 but saw 1/2
   I => 1
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 3 but saw 9/2
   I => 3
MY-SQRT: (MY-SQRT (* I I)) failed: Expected 4 but saw 8
   I => 4
MY-SQRT: 2 assertions passed, 3 failed.

The next most useful assertion form is

(assert-true test)

This tallies a failure if test returns false. Again, if you need to print out extra information, just add expressions after test.

There are also assertion forms to test what code prints, what errors code returns, or what a macro expands into. A complete list of assertion forms is in the reference and extensions sections.

Do not confuse assert-true with Common Lisp's assert macro. assert is used in code to guarantee that some condition is true. If it isn't, the code halts. assert has options you can use to let a user fix what's wrong and resume execution. A similar collision of names exists in JUnit and Java.

How to Organize Tests with Packages

Tests are grouped internally by the current package, so that a set of tests can be defined for one package of code without interfering with tests for other packages.

If your code is being defined in cl-user, which is common when learning Common Lisp, but not for production-level code, then you should define your tests in cl-user as well.

If your code is being defined in its own package, you should define your tests either in that same package, or in another package for test code. The latter approach has the advantage of making sure that your tests have access to only the exported symbols of your code package.

For example, if you were defining a date package, your date.lisp file would look like this:

(defpackage :date
  (:use :common-lisp)
  (:export :date->string :string->date))
  
(in-package :date)

(defun date->string (date) ...)
(defun string->date (string) ...)

Your date-tests.lisp file would look like this:

(defpackage :date-tests
  (:use :common-lisp :lisp-unit :date))

(in-package :date-tests)

(define-test date->string
  (assert-true (string= ... (date->string ...)))
  ...)
...

You could then run all your date tests in the test package:

(in-package :date-tests)

(run-tests)

Alternately, you could run all your date tests from any package with:

(lisp-unit:run-all-tests :date-tests)

Reference Section

The list of the basic functions and macros exported by lisp-unit are presented in this section.

Functions for managing tests

(define-test name exp1 exp2 ...)
This macro defines a test called name with the expressions specified, in the package specified by the value of *package* in effect when define-test is executed. The expresssions are assembled into runnable code whenever needed by run-tests. Hence you can define or redefine macros without reloading tests using those macros.
(get-tests [package])
This function returns the names of all the tests that have been defined for the package. If no package is given, the value of *package* is used.
(get-test-code name [package])
This function returns the body of the code stored for the test name under package. If no package is given, the value of *package* is used.
(remove-tests names [package])
This function removes the tests named for the given package. If no package is given, the value of *package* is used.
(remove-all-tests [package])
This function removes the tests for the given package. If no package is given, it removes all tests for the current package. If nil is given, it removes all tests for all packages.
(run-all-tests package)
This macro runs all the tests defined in the specified package and reports the results.
(run-tests name1 name2 ...)
This macro runs the tests named and reports the results. The package used is the value of *package* in effect when the macro is expanded. If no names are given, all tests for that package are run.
(use-debugger [flag])
By default, errors that occur while running tests are simply counted and ignored. You can change this behavior by calling use-debugger with one of three possible flag values: t (the default) means your Lisp's normal error handling routines will be invoked when errors occur; :ask means you will be asked what to do when an error occurs, and nil means errors are counted and ignored, i.e., the standard behavior.

Forms for assertions

All of the assertion forms are macros. They tally a failure if the associated predication returns false. Assertions can be made about return values, printed output, macro expansions, and even expected errors. Assertion form arguments are evaluated in the local lexical environment.

All assertion forms allow you to include additional expressions at the end of the form. These expressions and their values will be printed only when the test fails.

Return values are unspecified for all assertion forms.

(assert-eq value form [form1 form2 ...])
(assert-eql value form [form1 form2 ...])
(assert-equal value form [form1 form2 ...])
(assert-equalp value form [form1 form2 ...])
(assert-equality predicate value form [form1 form2 ...])
These macros tally a failure if value is not equal to the result returned by form, using the specified equality predicate. In general, assert-equal is used for most tests. Example use of assert-equality:
      (assert-equality #'set-equal '(a b c) (unique-atoms '((b c) a ((b a) c))))
    
(assert-true test [form1 form2 ...])
(assert-false test [form1 form2 ...])
assert-true tallies a failure if test returns false. assert-false tallies a failure if test returns true.
(assert-prints "output" form [form1 form2 ...])
This macro tallies a failure if form does not print to standard output stream output equal to the given string, ignoring differences in beginning and ending newlines.
(assert-expands expansion form [form1 form2 ...])
This macro tallies a failure if (macroexpand-1 form) does not produce a value equal to expansion.
(assert-error condition-type form [form1 form2 ...])
This macro tallies a failure if form does not signal an error that is equal to or a subtype of condition-type. Use error to refer to any kind of error. See condition types in the Common Lisp Hyperspec for other possible names. For example,
(assert-error 'arithmetic-error (foo 0))
would assert that foo is supposed to signal an arithmetic error when passed zero.

Utility predicates

Several predicate functions are exported that are often useful in writing tests with assert-equality.

(logically-equal value1 value2)
This predicate returns true of the two values are either both true, i.e., non-NIL, or both false.
(set-equal list1 list2 [:test])
This predicate returns true the first list is a subset of the second and vice versa. :test can be used to specify an equality predicate. The default is eql.

Extensions Section

The extensions to lisp-unit are presented in this section. The original lisp-unit has been extended with predicate functions and assertions to support numerical testing. The predicates can be used with assert-equality or with the corresponding assertions. All extensions are implemented using generic functions and consequently can be specialized for user classes.

Floating point predicates and assertions

The internal default value of epsilon is is twice the appropriate float epsilon (i.e. 2*single-float-epsilon or 2*double-float-epsilon). Epsilon can be controlled at a lexical level using the package variable *epsilon*. If *epsilon* is set to nil, the internal epsilon values are used. This is the default value of epsilon.

(float-equal data1 data2 [epsilon])
(assert-float-equal value form [form1 form2 ...])
Return true if the relative error between data1 and data2 is less than epsilon. The assertion tallies the failure if value is not equal to the result returned from form, using float-equal.
(sigfig-equal float1 float2 [significant-figures])
(assert-sigfig-equal value form [form1 form2 ...])
Return true if float1 and float2 are equal to the specified significant-figures. The default value of significant figures is 4, set by the global variable *significant-figures. The test tallies the failure if value is not equal to the result returned from form, using sigfig-equal.
(norm-equal data1 data2 [epsilon] [measure])
(assert-norm-equal value form [form1 form2 ...])
Return true if the relative error norm between data1 and data2 is less than epsilon.

GSLL Specific Predicates and Assertions

(number-equal number1 number2 [epsilon] [type-eq-p])
(assert-number-equal value form [form1 form2 ...])
Return true if the error between number1 and number2 is less than epsilon. The numbers must be the same type unless type-eq-p is t. For the comparison, both numbers are coerced to (complex double-float) and passed to float-equal. The test tallies the failure if value is not equal to the result returned from form, using number-equal.
(numerical-equal result1 result2 [:test])
(assert-numerical-equal value form [form1 form2 ...])
Return true if the numerical result1 is equal to result2 as defined by :test. The results can be numbers, sequences, nested sequences and arrays. The test tallies the failure if value is not equal to the result returned from form, using numerical-equal. In general, test must be a function that accepts 2 arguments and returns true or false.

Floating point functions

The floating point functions can be specialized for user data types and aid in writing user specific predicates.

(default-epsilon value)
Return the default epsilon for value.
(relative-error exact approximate)
Return the relative error.
(sumsq data)
Return the scaling parameter and the sum of the squares of the data.
(sump data p)
Return the scaling parameter and the sum of the powers of p of the data.
(norm data [measure])
Return the element-wise norm of the data.
(relative-error-norm exact approximate [measure])
Return the relative error norm.

Comments or suggestions?