One thing is constant in any language- the need to test the code.
In this system, a set of function names of tests are
stored in the *tests* global.
(defparameter *tests* nil)
When we define tests, we are actually writing a defun while, as a side effect, we addding the name of the function to a list of known tests.
(defmacro deftest (name params &body body)
"Create a defun, adding it name to the list of *tests*."
`(progn (unless (member ',name *tests*) (push ',name *tests*))
(defun ,name ,params ,@body)))
Running tests updates two counters: pass and P<code>.
The test function runs one test while tests runs them
all.
(let ((pass 0)
(fail 0))
(defun test (want got)
"Run one test, comparing 'want' to 'got'."
(labels
((white (c) ; returns nil if 'c' is not white space
(member c '(#\# #\\ #\Space #\Tab #\Newline
#\Linefeed #\Return #\Page) :test #'char=))
(whiteout (s) ; remove all white space
(remove-if #'white s))
(samep (x y) ; returns t if the blackspace of x&y, are the same
(string= (whiteout (format nil "~(~a~)" x))
(whiteout (format nil "~(~a~)" y)))))
(cond ((samep want got) (incf pass))
(t (incf fail)
(format t "~&; fail : expected ~a~%" want)))
got))
(defun tests ()
"Run all the tests in *tests*."
(labels ((run (x) (format t "~&;testing ~a~%" x) (funcall x)))
(when *tests*
(setf fail 0 pass 0)
(mapcar #'run (reverse *tests*))
(format t "~&; pass : ~a = ~5,1f% ~%; fail : ~a = ~5,1f% ~%"
pass (* 100 (/ pass (+ pass fail)))
fail (* 100 (/ fail (+ pass fail)))))))
)
(deftest ?deftest1 (&aux (a 1)) (test (+ a 1) 2))
In this test, we actually see a failure.
(deftest ?deftest2 (&aux (a 1)) (test (+ a 1) 3))
Note in the following, that the test calls some other function.
(deftest ?deftest3 ()
(test (my-complicated-thing)
'(3 4 5)))
(defun my-complicated-thing () (member 3 '(1 2 3 4 5)))
This code was inspired by
Peter Seibel's excellent defest system
(see http://gigamonkeys.com/book/practical-building-a-unit-test-framework.html).
Tim Menzies (tim@menzies.us)
Share and enjoy.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.