LispPad

Lightweight Scheme Development on macOS

LispKit Test

Library (lispkit test) provides an API for writing uni tests. The API is largely compatible to similar APIs that are bundled with popular Scheme interpreters and compilers.

Test groups

Tests are bundled in test groups. A test group contains actual tests comparing acual with expected values and nested test groups. Test groups may be given a name which is used for reporting on the testing progress and displaying aggregate test results for each test group.

The following code snippet illustrates how test groups are typically structured:


(test-begin "Test group example")
(test "Sum of first 10 integers" 45 (apply + (iota 10)))
(test 64 (gcd 1024 192))
(test-approx 1.414 (sqrt 2.0))
(test-end)

This code creates a test group with name Test group example. The test group defines three tests, one verifying the result of (apply + (iota 10)), one testing gcd and one testing sqrt. When executed, the following output is shown:


╒═══════════════════════════════════════════════════════════════
│ Basic unit tests
└───────────────────────────────────────────────────────────────
[PASS] Sum of first 10 integers
[PASS] (gcd 1024 192)
[FAIL] (sqrt 2.0): expected 1.414 but received 1.414213562373095
┌───────────────────────────────────────────────────────────────
│ Basic unit tests
│ 3 tests completed in 0.001 seconds
│ 2 (66.66%) tests passed
│ 1 (33.33%) tests failed
╘═══════════════════════════════════════════════════════════════

Procedure test-begin opens a new test group. It is optionally given a test group name. Anonymous test groups (without name) are supported, but not encouraged as they make it more difficult to understand the testing output.

Special forms such as test and test-approx are used to compare expected values with actual result values. Expected values always preceed the actual values. Tests might also be given a name, which is used instead of the expression to test in the test report. test, test-approx, etc. need to be called in the context of a test group, otherwise the syntactical forms will fail. This is different from other similar libraries which often have an anonymous top-level test group implicitly.

Here is the structure of a more complicated testing setup which has a top-level test group Library tests and two nested test groups Functionality A and Functionality B.


(test-begin "Library tests")
  (test-begin "Functionality A")
  (test ...)
  ...
  (test-end)
  (test-begin "Functionality B")
  ...
  (test-end)
(test-end)

The syntactic form test-group can be used to write small test groups more concisely. This code defines the same test group as above using test-group:


(test-group "Library tests"
  (test-group "Functionality A"
    (test ...)
    ...)
  (test-group "Functionality B"
    (test ...)
    ...))

Defining test groups

(test-begin) [procedure]
(test-begin name)

A new test group is opened via procedure test-begin. name defines a name for the test group. The name is primarily used in the test report to refer to the test group.

(test-end) [procedure]
(test-end name)

The currently open test group gets closed by calling procedure test-end. Optionally, for documentation and validation purposes, it is possible to provide name. If explicitly given, it has to match the name of the corresponding test-begin call in terms of equal?. When test-end is called, a summary gets printed listing stats such as passed/failed tests, the time it took to execute the tests in the group, etc.

(test-exit) [procedure]
(text-exit obj)

This procedure should be placed at the top-level of a test script. It raises an error if it is placed in the context of an open test group. If obj is provided and failures were encountered in the previously closed top-level test group, test-exit will exit the evaluation of the code by invoking (exit obj).

(test-group name body ...) [syntax]

test-group is a syntactical shortcut for opening and closing a new named test group. It is equivalent to:


(begin
  (test-begin name)
  body ...
  (test-end))

(test-group-failed-tests) [procedure]

Returns the number of failed tests in the innermost active test group.

(test-group-passed-tests) [procedure]

Returns the number of passed tests in the innermost active test group.

(failed-tests) [procedure]

Returns the number of failed tests in all currently active test group.

(passed-tests) [procedure]

Returns the number of passed tests in all currently active test group.

Comparing actual with expected values

(test exp tst) [syntax]
(test name exp tst)

Main syntax for comparing the result of evaluating expression tst with the expected value exp. The procedure stored in parameter object current-test-comparator is used to compare the actual value with the expected value. name is supposed to be a string and used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead. test catches errors and prints informative failure messages, including the name, what was expected and what was computed. test is a convenience wrapper around test-equal that catches common mistakes.

(test-equal exp tst) [syntax]
(test-equal name exp tst)
(test-equal name exp tst eq)

Compares the result of evaluating expression tst with the expected value exp. The procedure eq is used to compare the actual value with the expected value exp. If eq is not provided, the procedure stored in parameter object current-test-comparator is used as a default. name is supposed to be a string and it is used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead. test-equal catches errors and prints informative failure messages, including the name, what was expected and what was computed.

(test-assert tst) [syntax]
(test-assert name tst)

test-assert asserts that the test expression tst is not false. It is a convenience wrapper around test-equal. name is supposed to be a string. It is used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead.

(test-error tst) [syntax]
(test-error name tst)

test-error asserts that the test expression tst fails by raising an error. name is supposed to be a string. It is used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead.

(test-approx exp tst) [syntax]
(test-approx name exp tst)

Compares the result of evaluating expression tst with the expected floating-point value exp. The procedure approx-equal? is used to compare the actual value with the expected flonum value exp. approx-equal? uses the parameter object current-test-epsilon to determine the precision of the comparison (the default is 0.0000001). name is supposed to be a string. It is used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead. test-approx catches errors and prints informative failure messages, including the name, what was expected and what was computed.

(test-not tst) [syntax]
(test-not name tst)

test-not asserts that the test expression tst is false. It is a convenience wrapper around test-equal. name is supposed to be a string. It is used to report success and failure of the test. If not provided, the output of (display tst) is used as a name instead.

(test-values exp tst) [syntax]
(test-values name exp tst)

Compares the result of evaluating expression tst with the expected values exp. exp should be of the form (values x ...). As opposed to test and test-equal, test-values works for multiple return values in a portable fashion. The procedure stored in parameter object current-test-comparator is used as a comparison procedure. name is expected to be a string.

Test utilities

current-test-comparator [parameter object]

Parameter object referring to the default comparison procedure for test and the test-* syntactical forms. By default, current-test-comparator refers to equal?.

current-test-epsilon [parameter object]

Maximum difference allowed for inexact comparisons via procedure approx-equal?. By default, this parameter object is set to 0.0000001.

(approx-equal? x y) [procedure]
(approx-equal? x y epsilon)

Compares numerical value x with numerical value y and returns #t if x and y are approximately true. They are approximately true if x and y differ at most by epsilon. If epsilon is not provided, the value of parameter object current-test-epsilon is used as a default.

(write-to-string obj) [procedure]

Writes value obj into a new string using procedure write, unless obj is a pair, in which case write-to-string interprets it as a Scheme expression and uses shortcut syntax for special forms such as quote, quasiquote, etc. This procedure is used to convert expressions into names of tests.