Unit Testing in PLT Scheme

You can write tests for your code using the tools detailed in the PLAI documentation. Or, if you want, you can use SchemeUnit. This page walks you through writing unit tests in SchemeUnit, because it isn't that hard.

Create a test file

Assume your code is in the file assignment.ss. And, let's assume you've defined two functions in your assignment: one that adds numbers, and one that subtracts numbers.

;; CONTRACT
;; my-plus : number number -> number
(define (my-plus a b)
  (+ a b))
;; CONTRACT
;; my-minus : number number -> number
(define (my-minus a b)
  (- a b))
;; CONTRACT
;; my-divide : number number -> number
(define (my-divide a b)
  (/ a b))

To test the code in this file, you should first create a file called assignment-tests.ss. At the top of that file, we're going to start by importing three things:

  1. The SchemeUnit library.
  2. The SchemeUnit textual output interface
  3. Your code, in the file assignment.ss. (Or, whatever you named the file containing your code.)


Simple Tests

You are now ready to begin writing tests. The simplest kind of test we can write is one where we check whether the output of our functions are what we expect. In the file assignment-tests.ss, I'm going to write two simple tests. The whole file looks like this:

;; Import SchemeUnit and the assignment
;; code I wrote in the file "assignment.ss"
(require (planet schematics/schemeunit:3)
         (planet schematics/schemeunit:3/text-ui)
         "assignment.ss")
;; Some simple checks; the second test fails.
(check-equal? (my-plus 1 1) 2 "Simple addition")
(check-equal? (my-minus 1 2) 2 "Simple subtraction")


Now, I can hit "Run". This will import the code for SchemeUnit, the code I wrote, and then run my tests. I see the following:

20090111-simple-test-output

The first test passed, but the second failed, because the value returned from my function and the value I claimed it should be were different. Because this is trivial code, I know that my test is wrong... but in more complex cases, the question becomes whether your code is right, or your test is right... [queue scary music].

Organizing Tests

You can write everything as simple tests, or you can organize them into test suites.  (Prof. Kapfhammer's ears just perked up!) This will be valuable as you are writing your interpreters. You might have a set of tests that explore addition, and then you might group all your arithmetic tests into a one test suite, and so on. That way, if you want to, you can run just some of your tests, or all of your tests. This is what we call "manual test suite optimization."

To set up a test suite, I define a new variable to hold a test suite, and use the SchemeUnit test-suite syntax. You can see this in the (complete) example code below.

;; Import SchemeUnit and the assignment
;; code I wrote in the file "assignment.ss"
(require (planet schematics/schemeunit:3)
         (planet schematics/schemeunit:3/text-ui)
         "assignment.ss")
;; Arithmetic Test Suite
;; This test suite focuses on testing the
;; basic arithmetic functions.
(define arithmetic-tests
  (test-suite
   "Arithmetic Tests"
   (check-equal? (my-plus 1 1) 2 "Simple addition")
   (check-equal? (my-minus 1 2) 2 "Simple subtraction")
   ))
;; A collection of all of my
;; test suites.
(define all-tests
  (test-suite
   "All Tests"
   arithmetic-tests))
;; Run all of the test suites.
(run-tests all-tests)


There's More...

But you can read about it in the SchemeUnit documentation. There are many other kinds of tests (including tests that will test to make sure your interpreter throws exceptions). To demonstrate this, I've extended my test suite above with a new test:

(check-exn exn? (lambda () (my-divide 1 0)))

check-exn expects two arguments: a predicate that tests for an exception*, and then it takes a thunk. A thunk is a function of zero arguments. check-exn takes this function, evaluates it, and if an exception of the right type is thrown, it's happy! If not, it will complain that no exceptions were thrown.

* (See the PLT Documentation on exceptions for all the kinds of exceptions that can be thrown; you'll be throwing some of your own in authoring your interpreters. If the link I gave doesn't work, search for exn? in the documentation to find all of the exception types. They're just structures!)