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