123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- ;;; test-framework.lisp
- ;;; written by Peter Lane
- ;;; (c) School of Computer Science, University of Hertfordshire, 2007
- ;;; ABOUT THIS SOFTWARE
- ;;;
- ;;; The functions in this file support use of a testing framework for
- ;;; scientific theories. Tests are separated into three classes, as
- ;;; described in [1]:
- ;;; 1. Unit tests - for implementational details
- ;;; 2. Process tests - for the main processes within the theory
- ;;; 3. Canonical results - for the experimental results achieved by the
- ;;; theory
- ;;; [1] P.C.R. Lane and F. Gobet, Developing reproducible and comprehensible
- ;;; computational models, Artificial Intelligence, 144:251--63, 2003.
- ;;; INSTRUCTIONS FOR USE
- ;;;
- ;;; Individual tests are written in one of the following forms:
- ;;; (test THING-TO-CHECK [MESSAGE])
- ;;; where: THING-TO-CHECK is a test, returning a boolean value
- ;;; test should return T if the test passed
- ;;; MESSAGE is optional, and is given if the test is NIL
- ;;; (assert-= EXPECTED ACTUAL [MESSAGE])
- ;;; (assert-eq EXPECTED ACTUAL [MESSAGE])
- ;;; (assert-equalp EXPECTED ACTUAL [MESSAGE])
- ;;; (assert-string= EXPECTED ACTUAL [MESSAGE])
- ;;; where: EXPECTED is the predicted value
- ;;; ACTUAL is the computed value
- ;;; EXPECTED and ACTUAL are compared using respective function
- ;;; MESSAGE is optional, and given if the comparison fails
- ;;; (assert-true THING-TO-CHECK [MESSAGE]) is the same as (test THING-TO-CHECK MESSAGE)
- ;;; (assert-false THING-TO-CHECK [MESSAGE]) is the same as (test (not THING-TO-CHECK) MESSAGE)
- ;;; (assert-null THING-TO-CHECK [MESSAGE]) is the same as (test (null THING-TO-CHECK) MESSAGE)
- ;;;
- ;;; To create a group of tests, use either of:
- ;;; def-unit-tests
- ;;; def-process-tests
- ;;; def-canonical-result-tests
- ;;; All three do the same job, which is to define a function containing one or
- ;;; more calls to (test .. ..)
- ;;; The framework stores the functions in one of the three categories of tests.
- ;;;
- ;;; Tests can be run using either of:
- ;;; (run-unit-tests) evaluates every function defined using def-unit-tests
- ;;; (run-process-tests) evaluates every function defined using def-process-tests
- ;;; (run-canonical-result-tests) evaluates every function defined using def-canonical-result-tests
- ;;; (run-all-tests) evaluates all three of the above, running all tests
- (let ((error-count 0)
- (total-tests 0)
- (unit-tests ())
- (process-tests ())
- (canonical-results-tests ()))
- (defun reset-error-count ()
- (setf error-count 0
- total-tests 0))
- (defun error-feedback ()
- (format t "~%=== DONE: There ~a ~a error~a in ~a test~a~%"
- (if (= 1 error-count) "was" "were")
- error-count
- (if (= 1 error-count) "" "s")
- total-tests
- (if (= 1 total-tests) "" "s")))
- ;; the basic test function
- (defun test (bool &optional (msg ""))
- "If bool is true, display a dot, else the message"
- (incf total-tests)
- (if bool
- (format t ".")
- (format t "~&Error ~a: ~a~&" (incf error-count) msg)))
-
- ;; some customised test functions, to provide a more direct test syntax
- (flet ((test-compare (comp-fn expected actual &optional (msg ""))
- (test (funcall comp-fn expected actual)
- (format nil "Expected ~a got ~a. ~a" expected actual msg))))
- (defun assert-eq (expected actual &optional (msg ""))
- (test-compare #'eq expected actual msg))
- (defun assert-equalp (expected actual &optional (msg ""))
- (test-compare #'equalp expected actual msg))
- (defun assert= (expected actual &optional (msg ""))
- (test-compare #'= expected actual msg))
- (defun assert-string= (expected actual &optional (msg ""))
- (test-compare #'string= expected actual msg)))
- (defun assert-true (bool &optional (msg ""))
- (test bool msg))
- (defun assert-false (bool &optional (msg ""))
- (test (not bool) msg))
- (defun assert-null (item &optional (msg ""))
- (test (null item) msg))
-
- ;; macros for creating functions containing tests, placing the tests into groups
- (defmacro def-unit-tests (name &rest body)
- (push name unit-tests)
- `(defun ,name ,@body))
- (defmacro def-process-tests (name &rest body)
- (push name process-tests)
- `(defun ,name ,@body))
- (defmacro def-canonical-result-tests (name &rest body)
- (push name canonical-results-tests)
- `(defun ,name ,@body))
- ;; functions for running the tests
- (flet ((run-tests (name test-list)
- (format t "Running ~a: " name)
- (reset-error-count)
- (dolist (test test-list) (funcall test))
- (error-feedback)))
- (defun run-unit-tests () (run-tests "Unit tests" unit-tests))
- (defun run-process-tests () (run-tests "Process tests" process-tests))
- (defun run-canonical-result-tests ()
- (run-tests "Canonical results" canonical-results-tests)))
- (defun run-all-tests ()
- (run-unit-tests)
- (run-process-tests)
- (run-canonical-result-tests)))
- #|
- ;;; example of use
- (defun adder (x y) (+ x y))
- (defun multiplier (x y) (* x y))
- (def-unit-tests x ()
- (test (= 4 (adder 2 2)) "test 1")
- (test (= 5 (adder 2 3)) "test 2")
- (assert-equalp 4 (adder 2 2)))
- (def-unit-tests x2 ()
- (assert= 7 (adder 2 1))
- (assert= 7 (adder 2 1) "adding error"))
- (def-unit-tests x3 ()
- (assert-true (< 2 (adder 2 1)))
- (assert-false (> 2 (adder 2 1))))
- (def-process-tests y ()
- (test (= 4 (multiplier 2 2))))
- (def-canonical-result-tests z ()
- (test (= 20 (multiplier (adder 2 3) 4))))
- (run-all-tests) ;; runs all the defined tests, separated into groups
- ;; you can run an individual test function by calling it:
- ;; (x)
- ;; or the functions from one group by calling the relevant 'run' function
- ;; (run-unit-tests)
- |#
|