[gambit-list] Unit testing fun

Marc Feeley feeley at iro.umontreal.ca
Sun May 3 00:40:59 EDT 2009


On 1-May-09, at 4:40 AM, lowly coder wrote:

> Hi Gambiters,
>
>   I've been thinking for a long time about the simplest unit testing  
> framework I can think of. I came up with the following:
>
> ~/testing$ cat unit-test.scm
> (define (unit-test-create name)
>   (let ((unit-test-name name)
>         (tests '()))
>
>     (define (add name test)
>       (set! tests (cons (cons name test) tests)))
>
>     (define (run-tests)
>       (map (lambda (value)
>              (let ((x (car value))
>                    (y (cdr value)))
>                (pp `(,x ,(y)))))
>            (reverse tests)))
>
>     (define (dispatch . cmds)
>       (cond ((eq? (car cmds) 'run) (run-tests))
>             ((eq? (car cmds) 'add) (add (cadr cmds) (caddr cmds)))
>             ((#t (error ,(command ,cmds not supported))))))
>
>     dispatch))
> ~/testing$ cat a.scm
> (include "unit-test.scm")
>
> (define unit-tests (unit-test-create 'tests-a))
>
> (unit-tests 'add '+ (lambda () (eq? (+ 1 1) 2)))
> (unit-tests 'add '- (lambda () (eq? (- 1 1) 0)))
> (unit-tests 'add '* (lambda () (eq? (* 1 1) 2)))
>
> ~/testing$ gsi -e "(load \"a.scm\") (unit-tests 'run)"
> (+ #t)
> (- #t)
> (* #f)
>
>
>
> I'm almost happy with this, except that if I have a.scm and b.scm, I  
> have to have different names in them (I can't have them both use  
> 'unit-tests').
>
> Maybe I can call them 'unit-tests-a' or 'unit-tests-b' .. but then I  
> have to deal with folder path.
>
> Maybe I can call them 'unit-tests-testing-a' and 'unit-tests-testing- 
> b' but this becomes a pain when I move files around.
>
> What I want would be somehow, to have a variable created  
> automatically that's _local_ to the particular file, so that I can  
> still run something like:
>
> gsi -e "(load \"a.scm\") (magic)"
> or
> gsi -e "(include \"a.scm\") (magic)"
> or
> gsi -e "(magic1 \"a.scm\") (magic2)"
>
> and have the proper tests run.
>
> How can I do this?
>
> Thanks!


Add this to unit-test.scm:

(##define-syntax define-local
   (lambda (src)
     (if (not (##source? src))
         (error "expected a source object")
         (let ((locat (##source-locat src)))
           (if (not locat)
               (error "location unknown")
               (let ((container (##locat-container locat))
                     (position (##locat-position locat)))
                 (let* ((path (##container->path container))
                        (ns (path-strip-extension (path-strip- 
directory path)))
                        (local-namespace (string-append ns "#"))
                        (pattern (##desourcify (cadr (##source-code  
src))))
                        (id (if (symbol? pattern) pattern (car  
pattern)))
                        (rest (cdr (##source-code src))))
                   `(begin
                      (namespace (,local-namespace ,id))
                      (define , at rest)))))))))

and in a.scm use "define-local" like this:

(define-local unit-tests (unit-test-create 'tests-a))

For your unit test framework, in general equal? seems like a better  
choice for testing equality of the result with the expected result (at  
least for numbers as in your example).

Also, the use of macros might make for a more elegant expression of  
the tests, for example:

(unit-test (equal? 2 (+ 1 1)))

The use of a macro will allow you to display the actual test that  
failed.  Along the lines of:

(define-macro (unit-test expr)
   `(if (not ,expr) (begin (display "failed test: ") (pretty-print  
',expr))))

Of course this could be made much more robust (to catch exceptiosn for  
example).

Marc



More information about the Gambit-list mailing list