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

Unit Tests

;

(From Peter Seiblel's excellent text: ; http://gigamonkeys.com/book). ;

;

Q: What is the same across all languages?

;

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

Globals

;

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 ;

Running Test(s)

;

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

Defining One Test

;

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

Support Tools

;

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

Handling the Counters

;

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

Examples

;

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.