Dog-fooding a Domain Specific Language (DSL); creating an extension to Lisp-Unit

July 20, 2008

The code for lisp-unit-fixture is in my link in the side panel.

The reasons for me wanting to use a dsl to run unit tests created with lisp-unit is because of duplication:

  1. Having to write (define-test …) for every test.
  2. having to use setup ‘let’s in every test.

An example is like this:

(define-test adder
(let ((zero 0)
(one 1))
(assert-equal 1 (adder zero one))))

(define-test subtractor
(let ((zero 0)
(one 1))
(assert-equal -1 (subtractor zero one))))

See the duplication?

What I wanted to do is remove the duplication. The is the language I came up with:

(test-fixture match-operations

((zero o) (one 1)))


(assert-equal 1 (adder zero one)))

(assert-equal -1 (subtractor zero one)))))

Much more encapsulated and it removes the duplications.

A. Initial Steps

The way I did this was to use macros. Since I’ve just started using Lisp I thought it would be best If I took baby steps.

All the authoritative authors ;) recommend starting with the macro signature and then coming up with the expanded form:

((zero 0)(one 1))
(assert-equal 1 (adder zero one))))

This is the expanded form and this is the macro:

(defmacro create-test (setup test)
(let ((test-name (first test))
(test-code (rest test)))
`(define-test ,test-name
(let (,@setup)

This macro will extract test-name and test-code form from the test. The backqoute generates the define test and splices in the rest of the variable; test-name, setup and test code. The expanded code is this:

(define-test adder
(let ((zero 0)
(one 1))
(assert-equal 1 (adder zero one))))

B. Handling multiple tests


(setup ((true t) (false nil)))
(test-b (assert-true true) (assert-false false))
(test-c (assert-true true) (assert-false false))))


(defmacro create-tests (setup test-list)
(let ((test-collection (loop for i in (rest test-list)
collect `(create-test ,@(rest setup) ,i))))
`(values-list (list ,@test-collection))))

Expanded form:


Viola` multiple tests. Real simple huh? Well let me tell you this wasn’t so easy. Trying to get the loop to work right was crazy. I tried ‘do and ‘dolist. Nothing I did seem to work. I needed to unwrap each test from the collection. Cargo Cult to the rescue. I accidentally hit upon the idea of unwinding the collection with a value-list. i was still getting problems so I remember reading somewhere about splicing and then putting the splice in a list within the backquote. Woo Hoo! it worked. This area needs some research on my part.

Now on to…

C. Final Macro


(test-fixture run-two-test
(setup ((true t) (false nil)))
(test-d (assert-true true) (assert-false false))
(test-e (assert-true true) (assert-false false))))


(defmacro test-fixture (name setup &body body)
(setf name nil)
(if (not body)
`(create-tests ,(create-default-setup (gensym)) ,setup)
`(create-tests ,setup ,@body)))

Expanded form:


Alternate language:

(test-fixture run-two-test-no-setup
(test-h (assert-true t) (assert-false nil))
(test-i (assert-true t) (assert-false nil))))

Alternate expanded form:


Before we get started on some analysis, notice on the alternate expanded form you will see that setup is interned in lisp-unit-fixture package [NOTE: personally warned about this in private correspondence]. The alternate language doesn’t need to include setup code for inserting into each individual tests, but the macro will include the default. If a setup is not included in the test-fixture, that means the tests will be inserted into the setup variable.

Originally while writing this macro I used (if (equal ‘setup (first setup))) to find out if setup was in the test-fixture code. My original tests were also in the same package. The all would pass. I then used the test-fixture in some new code. Some of my new tests failed. 2 hours later I finally tracked it down. I moved my tests into it’s own package and then changed the implementation of the macro test-fixture.

Some references for this are here:

[ The Common Lisp Cookbook – Macros and Backquote ]

[ comp.lang.lisp ]

If you look at the code you will see that setup and should are just syntactic sugar placeholders. you can replace these markers with anything you want. Here is an example:

(test-fixture match-operations
(setup-for-all-tests ((zero o) (one 1)))
(adder (assert-equal 1 (
adder zero one)))
(subtractor (assert-equal -1 (
subtractor zero one)))))

D. Design decisions

One of the major problems that doesn’t sit well with me is the redundant let if no setup is included. But it works for now.


Lisp Content

July 19, 2008

I’ve now added content to this blog.

Some of the content consist of:

  1. Blogs I read concerning Lisp.
  2. Helpful errata.
  3. Download of Lisp that I program in.
  4. Some of my personal Lisp projects.

Status of Lisp as I See It

July 2, 2008

Dependency Management: Seems to be brittle.

Project Management: Weak also.

Mocking Framework: None. I found this: ‘el-expectations.el’ and ‘el-mock.el’

Unit Testing: I am presently using lisp-unit. I have found that this tool is pretty good. Sofar I’ve been able to change it’s output pretty easy. I changed the format of the output and then wrapped the macro in (time). I really like this tool.

Expectation/Mock Framework for Lisp

July 1, 2008

Tonight I just finished writing a nice piece of code. It allows me to create expectations/mock functions. here is an example:

(expect func (1 3) 4)

I started with this as my base line then built my macro for creating a mock function.

‘expect’ is the macro keyword.

‘func’ is the function to be mocked.

‘(1 3)’ is the argument list.

‘4’ is the return value.

Here is the macro with some helper functions:

(defmacro expect (fn-name args ret-val)
  (let ((arg-list (make-symbols args)))
    `(defun ,fn-name ,arg-list
       (let ((expected-args (list ,@args))
             (actual-args (list ,@arg-list)))
         (if (not (equal expected-args actual-args))
             (error (format-expectation-errors expected-args actual-args))

(defun format-expectation-errors (expected actual)
  (format nil “Incorrect Expection Args~% Expected: ~S~% Actual: ~S” expected actual))

(defun make-symbols (args)
  (loop for a in args collect (gensym))

The hardest part was to try and get the actual and expected args, but I did it with a little bit of cargo-cultism. evaluation occurs at run-time.

This is not a full blown framework it still need to have some type of controller to keep track of the order of expectations, but that will be for another evening.