My most recent coding, an arkanoid clone written in Common Lisp, with a sprite library that I'm creating with an eye towards reusability, called for unit testing. I know, one should always unit test, but at school I am often forced to use tools e.g. Visual Studio, that obfuscate the build process and make it time-consuming to set-up unit tests.
Judging from this comp.lang.lisp thread,
many lispers roll their own unit test frameworks.
In lisp it is fairly trivial (following code taken from the referenced comp.lang.lisp thread):
(defun pick-greater (x y) y) (defparameter *test-cases* '((+ (3 5) 8) (car ((3 5)) 3) (cdr ((3 5)) 5) (cons (3 5) (3 . 5)) (/ (6 4) 3/2) (cadr ((3 5 7)) 7) )) (remove-if #'(lambda (fn+args+res) (equal (caddr fn+args+res) (apply (car fn+args+res) (cadr fn+args+res)))) *test-cases*)
The last s-expression returns ((CDR ((3 5)) 5) (CADR ((3 5 7)) 7)) when evaluated,
showing which tests failed.
Honestly, when I saw this, I was pretty impressed.
A rudimentary, yet functional, testing framework in 4 lines of code.
Lisp continues to impress me.
Peter Seibel has a chapter in Practical Common Lisp devoted to creating a unit-testing library. He demonstrates proper use of macros (the lisp kind, unrelated to the C kind).
I wanted something a bit more feature-full, yet still simple, so I took a look around
at the available libraries. lisp-unit fits the bill.
There is another page I referenced here.
The big takeaway from that page was the use of (use-package :lisp-unit)
at the start.
I loaded the package using quicklisp: (ql:quickload :lisp-unit).
The main point of this post has surprisingly, against the best advice from writers of prose, not been introduced yet. The second page had a link to this "Xtreme" Programming article. While I agreed with most of the first portion, especially how writing unit test before code forces the requirements to be fully specified, I disagreed later on, mostly with the last paragraph:
There is a rhythm to developing software unit test first.
You create one test to define some small aspect of the problem at hand.
Then you create the simplest code that will make that test pass.
Then you create a second test.
Now you add to the code you just created to make this new test pass, but no more!
Not until you have yet a third test.
You continue until there is nothing left to test.
The coffee maker problem shows an example written in Java.
The code you will create is simple and concise, implementing only the features you wanted.
Other developers can see how to use this new code by browsing the tests.
Input whose results are undefined will be conspicuously absent from the test suite.
So they are basically saying that all my funtions should be discrete, the domain of which consists entirely of points for which there are test cases. No! This is not the understanding that other people using your code will have. They will assume that your functions will work for all values of the input types specified. For example, I would expect, say, a function that determined if a point in a rectangle, to work given any point and any rectangle. Or at the very least, any point and any rectangle with a positive (non-zero) area. This is orthogonally opposed to the test-spec driven function being promoted in the article.
Oh, but we can go further. I can trivially follow the above advice by hard-wiring in output to match the test spec. Think of it like a function defined by a set of ordered pairs, (input output). Hopefully everyone can easily concieve of the folly of this sort of philosophy.
This concludes Rant #269.4, entitled "Test Specs do not constitute a function definition."
