Accueil Blog Contact
fil RSSAbonnez-vous via RSS

Entrées récentes

Archives

Unit testing with Scheme part 2

Implementing a unit testing framework is fun to implement in Scheme. With a 100 lines of Scheme code, you are basically up and running. See my previous blog about unit testing in Scheme

One of the improvement is to provide access to the currently running test case. As naive this feature seems to be, it brings some interesting implementation discussion. So how do you go about implementing this ?

One simple solution is to have the currently running unit test in a global variable. The code looks like this:

(define *unit-test* #f)

(define (the-unit-test) *unit-test*)

(define (run-all-test!)
  (for-each (lambda (t) (set! *unit-test* t) ((unit-test-thunk t)))
            *unit-tests*))

In the running unit test, you simply call (the-unit-test). All fun, but what's wrong with this solution ? We have a global variable ! Everybody knows that global state is bad and you are better without it.

Now that you have multi-core processors, you may be tempted to run your unit tests suite in parallel. But how do you go about it if you have a global variable ? You can't.

So the standard solution is to pass around the running unit test instance around. The implementation goes like this:

(define (run-all-tests!)
  (for-each (lambda (t) ((unit-test-thunk t) t)) *unit-tests*))

(define-syntax define-unit-test
  (syntax-rules ()
    ((define-unit-test ?name ?doc ?body)
     (add-unit-test! (make-unit-test '?name ?doc ?body)))))

(define-unit-test test-string=?
  "Tests the string=?"
  (lambda (unit-test)
    (assert-true! unit-test (string=? "Hello" "Hello"))))

Some comments are required in order to fully grasp this design. The first change is that the running test is passed as the first parameter to the code performing the test cases.

This leads to the second change: the definition of the define-unit-test macro. The code for the unit test is now a lambda expression in itself. This is necessary because syntax-rules only supports hygienic macro definition. Having the name of the unit test variable defined implicitly requires a non-hygienic macro. Thus you should use a syntax-case macro facility or the explicit renaming macros.

The last change is that now the assert-xxx procedure takes an additional parameter (the currently running unit test). This makes it possible to print the currently running unit test name when some assertion fails !

Now you have a nice implementation which makes running more than one unit test concurrently possible. So you may take advantage of the multiple cores of your processor.

Let say that you want some statistics about your unit tests, like how many assertions have been done, the number of unit tests run and the ratio of failed/passed unit tests. If you think about it, you should recognize the statistic collector as another global object that should be accessible when running your unit tests.

You use a global variable for this. But as we have seen, this is not a good solution. You add another parameter to the running unit test and you have 2 parameters to pass around. To simplify the implementation, you may define a combo datastructure containing the running unit test and the collected statistics.

This principle is fine, but scales poorly. Maybe in the future, you want to provide another kind of information within the running tests. So you add another parameter or another entry in your combo datastructure. Finally you have thousands of parameters/entries in your solution. Isn't there a more simple and scalable solution ? In a following blog entry, you will know about a unique feature (almost) of the lisp family of language: dynamic varables.

Tracing facility for Scheme

Trace support is a nice debugging tool. When you trace a function, you make it so that each time the function is called you print out the function name and the value of its arguments. When the function finally returns, the return value is also printed out.

A scrennshot showing the result of tracing a function appears at the bottom.

Implementing a basic trace facility is quite simple and is also a nice example of higher-order programming. The tracify procedure is the heart of the system. It takes a procedure as a parameter (the procedure to trace) and returns a new procedure performing the tracing.

(define (tracify proc)
  (lambda args
    (display "Entering with arguments:") (write args) (newline)
    (let ((result (apply proc args)))
      (display "Leaving => ") (write result) (newline)
      result)))

Now that you can transform a procedure into one which does tracing, you need a command/procedure to enable tracing. It turns out, you can't do that with a procedure. Think about it. What is the argument ? a procedure, but then you need to store back the traced procedure. If you pass it a symbol for the procedure's name, how do you grab the procedure associated with the symbol ?

The easy solution is to write a macro for that.

(define-syntax trace
  (syntax-rules ()
    ((trace ?symbol) (set! ?symbol (tracify ?symbol)))))

You have now a nice tracing facility. What is missing ?

  • you may want to support procedure which returns multiple values.
  • you may want to indent the recursive calls as show in the screenshot above.
  • you may want to untrace a procedure. That is replacing the procedure with the original one.
  • you may want to list all the currently traced procedures.
tracing screenshot

Unit Testing framework in Scheme

Unit testing has become a standard tool for testing an piece of code. Implementing a Scheme solution for unit testing is simple and fun. You define a domain specific language for describing unit tests. This is completly done with macros. It looks something like this:

(define-unit-test testing-string=?
  "testing simple cases of string=? procedure"
  (assert-true! (string=? "Hello" "Hello"))
  (assert-false! (string=? "Hello" "world")))

Basically you specify a unit test name, testing-string=? here, together with a documentation string. Then comes a Scheme code performing the tests. You use the different flavours of the assert-xxx! procedure.

The implementation looks as follow. The main datastructure is the unit-test record type.

(define-record-type 
  (make-unit-test name doc thunk)
  unit-test?
  (name unit-test-name set-unit-test-name!)
  (doc  unit-test-doc  set-unit-test-doc!)
  (thunk unit-test-thunk set-unit-test-thunk!))

You should recognize here the different parts of the define-unit-test macro. In fact the macro basically creates instances of the <unit-test> record and store them in a table. Here is the macro definition:

(define-syntax define-unit-test
  (syntax-rules ()
    ((define-unit-test ?name ?doc . ?body)
     (add-unit-test! (make-unit-test '?name ?doc (lambda () . ?body))))))

All the unit tests defined are stored in a global variable.

(define *unit-tests* '())

(define (add-unit-test! test)
  (set! *unit-tests* (cons test *unit-tests*)))

Running all the unit tests defined so far is quite simple:

(define (run-all)
  (for-each (lambda (t) ((unit-test-thunk t))) *unit-tests*))

Writing unit tests requires the different assert procedures. They are simply implemented as procedures.

(define (assert-true! value)
  (if (not value) (display "error: assert-true! violation")))

(define (assert-false! value)
  (if value (display "error: assert-false! violation")))

(define (assert! expected effective)
  (if (not (equal? expected effective))
      (display "error: assert violation!")))

That's it for the implementation. In under 50 lines of Scheme code you have a basic unit testing framework: that's fun or not ? Moreover this is a solution that runs on any R5RS Scheme system with SRFI-9 record type definition: Scheme48, PLT Scheme, MIT-Scheme, etc...

This implementation has some limitations though. You may want some of the following features if you want a solid unit testing framework:

  • Some kind of output/trace/statistics when running all unit tests. This makes running all tests more appealing. Having the number of run tests displayed, togehter with the currently running tests and its result (passed/failed) would be nice. Some basic modifications of the run-all procedure may go a long way toward this goal.
  • When some assertions are violated, some kind of debugging output in order to better understand what happened. Here you may transform the different assert procedure into macros, so that in case of failure you can display the failing expression. Something like: (string=? "Hello" "World") failed, expected #f, evaluated to #t.
  • An ability to define some test suites. Test suites are subset of all unit tests. When doing development you may not be interested in running all unit tests, because only some unit tests are necessary to make sure your modification are safe. Also running all unit tests make take some time. What good it is to unit test a web server, when changing a GUI library. Thus having a GUI tests suite and web server test suite may be quite usefull.
  • Fixtures. When running a unit test, you may need to set your environment in a specific state so that your test run successfully. When defining unit tests, you may want to create some entries in a database or starting a web server, in order for your tests run properly.

Higher Order Programming in Scheme

I have been reading "Higher Order Perl Programming" lately. One of the example of using higher order functions is a file system traversing. Given a directory as a starting point you call a user specified function for each of the files in the directory. In case you encounter a directory then do the same for all of its files and so on.

Here is the Scheme version of this functions:

(define (for-each-file path proc)
  (cond 
   ((file-directory? path)
    (let ((names (directory-files path)))
      (for-each (lambda (p)
		  (if (or (string=? p ".") (string=? p ".."))
		      'do-nothing
		      (let ((complete-path (string-append (file-name-as-directory path)
							  "/"
							  p)))
			(for-each-file complete-path proc))))
		names)))
   (else (proc path))))

Then you can use it to display the list of HTML files in a given directoy with:

(for-each-file "." (lambda (f) (if (string=? ".html" (file-name-extension f))
				   (display f))))

Note that you may find all the Texinfo (filenames with extension ".texi") files in a directory with:

(for-each-file "." (lambda (f) (if (string=? ".texi" (file-name-extension f))
				   (display f))))

Note that you may do the same for all the Scheme or Perl or whatever file kind in a given directory. Is there not a pattern here ? Of course, you can come up with the following function:

(define (find-files-with-extension path extension)
  (for-each-file path (lambda (f) (if (string=? extension (file-name-extension f))
				      (display f)))))

Now you can simply call the function and pass it the desired file extension. Our examples now becomes:

(find-files-with-extension "." ".html")
(find-files-with-extension "." ".texi")
(find-files-with-extension "." ".scm")
(find-files-with-extension "." ".pl")

Let's go a bit further. What happens if you want to name such functions. Something like:

(define (find-html-files path) (find-files-with-extension path ".html"))
(define (find-texinfo-files path) (find-files-with-extension path ".texi"))
(define (find-scheme-files path) (find-files-with-extension path ".scm"))

You can achieve the same result with a higher order procedure. Why don't we construct a builder function: give it an extension and it gives you a procedure for find all files having this extension in a directory.

(define (make-file-finder-with-extension extension)
  (lambda (path) (find-files-with-extension path extension)))

Now our specific procedures find-html-files, find-textinfo-files, find-scheme-files may be written like that:

(define find-html-files (make-file-finder-with-extension ".html"))
(define find-texinfo-files (make-file-finder-with-extension ".texi"))
(define find-scheme-files (make-file-finder-with-extension ".scm"))

Just give me an extension and I give you a functions for finding all files in a directory having this precise extension. Give me X and I give you a function that Y. Isn't it nice ?

List comprehension in Scheme

Reading the Python code of Collective intelligence you see a lot of list comprehension code in it. In Scheme you don't have list comprehension at all. So you are screwed you might think.

Not at all. In fact, it is quite easy to add list comprehension to the language. You even don't need to go through a PEP process, JSR specification or wait for Ruby to include it.

You have Scheme macros at your disposition. Note that you already have SRFI-42 which provides all you need. Here we will implement our own system just to show how nice and simple it is to extend the Scheme language with a new syntax construct.

The implementation is based on Implementation of a Lisp comprehension macro from Guy Lapalme.

First let see how a Python expression is translated:

[e for e in [1,2,3,4,5,6,7,8,9,0] if e % 2 == 0]
The equivalent Scheme code is:
(:ec e (e <- '(1 2 3 4 4 5 6 7 8 9 0)) (= 0 (modulo e 2)))

To be able to run this code you need the following definitions:

(define-syntax :ec-aux
  (syntax-rules  (<-)
    ((:ec-aux (?exp) ?list) 
     (cons ?exp ?list)) ;; rule A
    ((:ec-aux (?exp (?v <- ?l1) . ?rest) ?l2)
     (letrec ((h (lambda (us)
                   (if (null? us) 
                       ?l2
                     (let ((?v (car us))
                           (us* (cdr us)))
                       (:ec-aux (?exp . ?rest) (h us*)))))))
             (h ?l1)))
    ((:ec-aux (?exp ?pred . ?rest) ?list)
     (if ?pred (:ec-aux (?exp . ?rest) ?list) ?list))))


(define-syntax :ec
  (syntax-rules ()
    ((:ec . ?term) (:ec-aux ?term '()))))

It is remarkable how you can implement such a feature in 19 lines of code. I wonder how long the implementation is in the C Python implementation.