;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This file is part of AIslash.
;
; AIslash 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.
;
; AIslash 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 AIslash. If not, see
(From Peter Seiblel's excellent text: ; http://gigamonkeys.com/book). ;
;
A: all programs in all languages need testing. ;
I should be able to write a test in any language. ; --J. Random Hacker ;
Every programming has to be debugged: ; ; ;
; Wilkes is pointing out that the time required to debug a system is ; surprisingly large- over half the effort of building. ; According to Brooks (The Mythical Man Month, 1995), the time ; required to build software divides as follows: ;
Decades later, the same distribution persists. It looks like this: ;
What this diagram is saying is that if you want to drastically change ; the economics of software development, do something about ; testability. ; So dealing with a new language, ; always start with the test engine. ;
Peter Seiblel's deftest test engine is both darn useful ;and a good beginner's ;test for LISP and LISP design. He took a test engine, found common ;patterns, and wrote macros to implement those common features. ;
Macros are a little beyond most LISP begineers so, if the following, ;do not try to understand it all- just test yourself to see how much ; you do understand. ;
Place to store (1) a list of active tests and (2) a list of all test names. ;
(defparameter *test-name* nil) (defparameter *all-tests* nil) ;;
Q: what is the difference between defparameter, ; defvar and defconstant? Hint: usually use ;defparameter ;
Every test does the following.. ;
(defmacro run-tests (&body body) `(progn (make) ; check you have the system updated (tests-reset) ; reset some counters to zero ,@body ; run the code (tests-report))) ; report the state of the counters ;;
Q: in the above, what does "`" and ",@" do? Hint1: this is ; really tricky. Hint2: run-tests ; does not exectute; rather it does some compile-time rewrites. ;
Usually, we just run tests which ; runs over all all the *all-tests* and runs one? ;
(defun tests () (run-tests (dolist (one (reverse *all-tests*)) (funcall one)))) ;;
Q: Why do we call reverse in the above? Hint: ;the name of new tests is pushed onto *all-tests* in ; the order of their arrival. ;
Q: What does funcall do? Hint: pointer to functions. ;
When we define tests, we have to store its name in *all-tests* ; then write a special defun using that test name. ; Inside this defun, before we run a test, we push the name ; to the end of a list of active tests. ;
(Note that the explanation actually takes more characters ; than the actual code. Tee hee.). ;
(defmacro deftest (name parameters &body body) `(progn (setf *all-tests* (remove-duplicates (cons ',name *all-tests*))) (defun ,name ,parameters (let ((*test-name* (append *test-name* (list ',name)))) ,@body)))) ;;
A little complex. Exercise for the reader. ;
(defmacro check (&body forms) `(combine-results ,@(loop for f in forms collect `(report-result ,f ',f)))) (defmacro with-gensyms ((&rest names) &body body) `(let ,(loop for n in names collect `(,n (make-symbol ,(string n)))) ,@body)) (defmacro combine-results (&body forms) (with-gensyms (result) `(let ((,result t)) ,@(loop for f in forms collect `(unless ,f (setf ,result nil))) ,result))) ;;
Object-oriented encapsulation can be implemented in LISP using ;closures. In the following, only these functions can access ; the number of passes and fails. ;
(let ((passes 0) (fails 0)) (defun tests-reset() (setf passes 0) (setf fails 0)) (defun tests-report () (if (zerop (+ passes fails)) (format t "no tests~%") (format t "~%PASSES: ~a (~a %)~%FAILS : ~a~%" passes (* 100 (/ passes (+ passes fails))) fails)) (>= passes fails)) (defun report-result (result form) (if result (and (incf passes) (format t "% ~a~%" *test-name*)) (and (incf fails) (format t "~%fail ... ~a: ~a~%" *test-name* form))) result) ) ;;
Q: what do these three functions do? ;
For examples of deftest in action, see ;random.lisp and ;hash.lisp. ;The hash.lisp ; example shows one interesting feature of deftest: it can be ; used to document the expected behaviour.