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/.