Building a Test Suite in Scheme

Although I don’t get to write in them a lot, I love LISPs. The language is amazingly simple and effectively a white canvas that you can build anything in.

This weekend I hopped back into reading The Structure and Interpretation of Computer Programs (SICP) and started implementing some of the problems. I was constantly copying and pasting (or worse, retyping) my test code. This was getting a little annoying so I figured I could implement a simple test suite to verify my algorithms are working.

Building a test suite in a language isn’t hard; heck, the Ruby testing library Minitest is a good example in the simplicity. There are lots of features you can build out, but for academic purposes, building a test suite shouldn’t be too tricky.

For my Scheme test suite the only thing I needed to do was ensure that each test returned true or false. If it’s false, then I know the test failed and I could act appropriately. The simplest way we can define our suite of tests would be a list of function names that we evaluate.

;; I am using the Racket Scheme Implementation
(define (suite cases)
    (if (eq? cases null)
        ("Test Suite passed Successfully!")
        (let ([testcase (car cases)])
            (if (testcase)
                (suite (cdr cases))
                (failure testcase)))))
(suite (cons test1 (cons test2 (cons test3 null))))

(define *tests* null)
(define (test code)
    (set! *tests* (cons code *tests*)))

While the above implementation works, it is pretty nasty to look at. It would be cool if we had a nicer way to define our tests. My first stab at the implementation simply used a global tests variable that I would append my test functions to. I had to major problems with this implementation: unnecessary test noise and tricky reporting. The noisy tests meant I needed to do two things in order for a test to be registered:

(define (pascal row col) -1)
(define (pascals-triangle-0-0) (= 1 (pascal 0 0)))
(test pascals-triangle-0-0) ;; ಠ_ಠ

I could’ve tried getting around it by simply passing in lambdas, but my suite didn’t have a good way of reporting which test had failed. Running a failing suite with a lambda in it would result in the following:

(suite (cons (lambda () #f) null))
;; "Test failed #<procedure>"

Well that’s not very handy so what if we were to take a different approach to building out these tests. Instead of our test simply taking a function, let’s pass in the name of the test and the code to run:

(define (test name code)
        (set! *tests* (cons (cons name code) *tests*)))

(define (suite cases)
    (if (eq? cases null)
        (let ([testcase (car cases)])
            (if ((cdr testcase))
                (suite (cdr cases))
                (fail (car testcase))))))
    "that 1+1 is 2"
    (lambda () (= 2 (+ 1 1))))
(suite *tests*)
;; "Test Suite Passed sucessfully"
    "that 2+2 is 5"
    (lambda () (= 5 (+ 2 2))))
(suite *tests*)
;; "Test failed that 2 + 2 is 5"

And with that is a super simple test suite. For more complicated tests it probably wouldn’t stand up, but for the kinds of functional programming I’ve been doing along with SICP it should work out alright.