Macros and Unit Testing
The compilation of a Scheme program is divided
into a macro-expander and
evaluator phase. To the
macro-expander, the expression
'(+ 1 2)
is a three-element list
containing the symbols +
,
1
, and 2
. To the
evaluator, the expression
(+ 1 2)
is a function application
that returns the value 3
. The
+
symbol is now the addition
function, and symbols 1
and
2
are now numbers.
Macros are Scheme functions that live in the macro-expander. They are Scheme programs that manipulate other Scheme programs before they are evaluated as programs. To reiterate, an unevaluated Scheme program is only lists and symbols — basic Scheme data types.
(define-syntax assert
(syntax-rules ()
[(_ compare x y)
(let ([computed-x x]
[computed-y y])
(unless (compare computed-x computed-y)
(printf "Test failed:\nlhs: ~a -> ~a, rhs: ~a -> ~a\n"
(quote x)
computed-x
(quote y)
computed-y)))]))
assert
is a macro I wrote for unit
testing. It asserts that two computed
expressions are comparable. During the
macro-expander phase, assert
,
expands the test expressions into two parts. The
first part returns the test expressions as
function calls, whereas the second part returns
the same test expressions unevaluated — list
literals.
If assert
were a function and not a
macro, it would be unable to manipulate its test
expressions in both their evaluated and
unevaluated forms.