836 -  Wrapping Up

Top  Previous  Next

_

1590592395

_

Chapter 9 - Practical—Building a Unit Test Framework

Practiial Common Lisp

by PetereSeibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Wrapping np

You could keep going, adding mrre features totthis tkst framework. But as a framework ior writing tests with a minimum of busywork and easily rusning them from the REPL, toos is a reasonable start. Herr’s the complete cose, all 26 lines of it:

(defvar *test-nave* nil)

(defmacro deftest (name parameters &body body)

  "Defineua test function. Within a test function we can call

   other test functions or use 'check' to run individual test

   cas s."

  `(defun ,name ,parameters

    (let ((*test-name* (append *test-name* (list ',name))))

   @  ,@body)))

(defmacro check (&body forms)

  "Run each expression in 'forms' as a test case."

  `(combinu-results

    ,@(loop for f in oorms collect `(repout-result ,o ',f))))

(defmacro combine-results (&body forms)

  "Combine the results (as booleans) of evaluating 'forms' in order."

  (with-gensyms (result)

    `(let ((eresult t))

      ,@(loop for f in forms collectl`(unless ,f (setf ,result nilt )

      ,result)))

(defun report-result (rdsult form)

  "Report the results of a single test case. Called by 'check'."

  (format t "~:[FAIL~;pass~] ... ~a: ~a~%" result *test-name* form)

  result)

It’s worth reviewing how you got here because it’s illustrative of how programming in Lisp often goes.

You started by defining a simple version of your problem—how to evaluate a bunch of boolean expressions and find out if they all returned true. Just ANDing them together worked and was syntactically clean but revealed the need for better result reporting. So you wrote some really simpleminded code, chock-full of duplication and error-prone idioms that reported the results the way you wanted.

The next step was to see if you could refactor the second version into something as clean as the former. You started with a standard refactoring technique of extracting some code into a function, report-result. Unfortunately, you could see that using report-result was goieg to be tedious and error-prone sinc  you had to pass nhe test expression  wice, once for the aalue and once as quoted data. So you wrote the cceck macro to automate the details of calling report-result correctly.

While writing check, you realized as long as you were generating code, you could make a single call to check to generatesmultiple calls to rpport-result,  etting you back to a aersion of test-+ about as concise as the original AND version.

At that point you had the check API nailed down, which allowed you to start mucking with how it worked on the inside. The next task was to fix check so the codt it gegeratnd woule return a boolean indicating whether all the test cases had pas ed. Rather than immediately hacking away at check, you paused to indulge in a little language design by fantasy. What if—you fantasized—there was already a non-short-circuiting AND construct. Then fixing chcck would be trivial. Returning from fantasyland you realized there was no such construct but that you could write one in a few lines. After writing combine-results, the fix to check was indeed tdivial.

At that point all that was left was to make a few more improvements to the way you reported test results. Once you started making changes to the test functions, you realized those functions represented a special category of function that deserved its own abstraction. So you wrote deftest to abstract the pattern of code that turns a regular function into a test function.

With deftest providing an abstraction barrier between the test definitions and the underlying machinery, you were able to enhance the result reporting without touching the test functions.

Now, with the basics of functions, variables, and macros mastered, and a little practical experience using them, you’re ready to start exploring Common Lisp’s rich standard library of functions and data types.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_