==========================
== Zhuo Hong Wei's Blog ==
==========================
Any and everything

Refactoring Test Lib

After learning a little more, I decided to apply what I have learnt to make our barebones testing library even more minimal.

Before (LOC 14):

(defmacro make-test (name predicate)
    `(lambda () 
        (let ((p ,predicate))
            (if p 
                (format t "Test `~A` passed~%" ,name)
                (format t "Test `~A` failed~%" ,name))
        p)))

(defun run-tests (&rest tests)
       (let*  ((score (lambda (p) (if p 1 0)))
                (run-test (lambda (test) (funcall test)))
                (scores (map 'list score (map 'list run-test tests)))
                (num-tests (length scores))
                (num-passes (reduce #'+ scores)))
            (format t "~A out of ~A tests passed~%" num-passes num-tests)))

After (LOC 12):

(defmacro make-test (name predicate)
    `(lambda () 
        (values ,name ,predicate)))

(defun report-test (name passp)
    (format t "Test `~A` ~:[failed~;passed~]~%" name passp))

(defun run-test (test)
        (multiple-value-bind (name p) (funcall test) 
            (apply #'report-test `(,name ,p)) p))

(defun run-tests (&rest tests)
       (let  ((outcomes (map 'list #'run-test tests)))
            (format t "~A out of ~A tests passed~%" (count t outcomes) (length outcomes))))

Even though lines of code didn’t change much, I love the new version for a number of reasons.

  1. Test reporting is now a separate function, and it uses format’s conditional formatting.
  2. Use of values to return multiple values and multiple-value-bind to consume them.
  3. Use of backtick to quote a list while forcing evaluation of name and p versus the more verbose form: (list name p).
  4. No mapping of boolean to 1 or 0 and counting scores, simply using count to count number of true values.