Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
history
You can edit this page using
wiki syntax
for markup.
Article contents:
== Outdated egg! This is an egg for CHICKEN 3, the unsupported old release. You're almost certainly looking for [[/eggref/4/embedded-test|the CHICKEN 4 version of this egg]], if it exists. If it does not exist, there may be equivalent functionality provided by another egg; have a look at the [[https://wiki.call-cc.org/chicken-projects/egg-index-4.html|egg index]]. Otherwise, please consider porting this egg to the current version of CHICKEN. [[tags: egg]] == embedded-test [[toc:]] === Introduction This is yet another framework for defining unit tests for the Scheme programming language (in particular, [[http://wiki.call-cc.org|the Chicken implementation]]). Most testing frameworks require tests to be defined in their own files, tipically one file with tests per module; this one was created on the idea that the best location for test definitions is preceding the procedures they test. Tests serve as documentation of the expectations of procedures, specially in unusual cases. The most natural location for documentation about a procedure is immediately before its definition, which is where most programmers will embbed comments describing it. By placing the tests in that location, they serve as examples of usage in common and unusual cases, which increases their value. Furthermore, placing tests adjacent to the procedures they test makes it less likely for a programmer to forget to update them when he changes the procedure tested. Another design decision of this framework is to make tests part of the compiled program or library. Tests will typically require very little additional space in a binary, which we consider worth paying for the convenience of not having to build a separate binary and libraries for the purpose of testing a program. Of course, this doesn't mean the tests will always run ---to run them, one will set the {{TESTS}} environment variable---, but they will be present in binaries and libraries. In other words, while it would be trivial to extend this framework to allow compilation without tests, we've purposefully decided not to do so. A third important design decision we've taken is performing the evaluation of the tests only after all code has been loaded. Most testing frameworks will run the tests as soon as their definition is found; this one delays the evaluation until the {{run-tests}} procedure is called (which programs should do as part of their initialization). This allows testing of mutually recursive libraries. === Usage To use this testing framework in your program or library, you should use the {{test}} macro once per test. It takes as arguments: * The test expresion that you want to evaluate. During the evaluation of this expresion, the symbol “{{test-fail}}” will be bound to a procedure that will abort the test and cause it to fail; you should pass to this procedure arguments describing the reason for the abortion of the test. * The expected result. This is optional: if you don't specify, the test is treated as an assertion, which means that any value other than {{#f}} will cause the test to pass. * A comparison procedure. The default is {{equal?}}. Here are a few examples: <enscript highlight=scheme> (test (list? (list 1 2 3))) (test (string-split "a:b:c" ":") (list "a" "b" "c")) (test (stream-filter even? (stream 1 2 3 4 5)) (stream 2 4) (cut stream= char=? <...>)) </enscript> Related tests should be grouped using the {{test-group}} macro. The macro expects a name for the group of tests to be followed by the actual tests. An example is provided below. Typically the name will be the name of the procedure tested. The use of {{test-group}} is optional. Defining tests doesn't do anything by itself: at some point you have to call the {{run-tests}} procedure. This will cause the tests to be executed if the {{TESTS}} environment variable is set. If the {{TESTS_VERBOSE}} variable is set, information about each test will be printed. You should call the {{run-tests}} procedure when all your modules have been loaded and your environment is ready for the execution of your program. You can control which tests to run by setting the {{TESTS_GROUPS}} variable to a list of the groups of tests that you want to run. ==== Example Here is an example of a full program, with some tests: <enscript highlight=scheme> (use embedded-test) (test-group square (test (square 0) 0) (test (square 1) 1) (test (square 5) 25)) (define (square x) (* x x)) (test-group fact (test (fact 0) 1) (test (fact 1) 1) (test (fact 5) (* 5 4 3 2 1))) (define (fact x) (if (= x 0) x (* x (fact (- x 1))))) (run-tests) (display (fact (square (read)))) (newline) </enscript> Running the above program with the {{TESTS}} environment variable set will produce the following output and abort the execution, showing that some of the tests failed due to the bug in the {{fact}} procedure: Test from group fact failed: (fact 0) Expected: 1 Received: 0 Test from group fact failed: (fact 1) Expected: 1 Received: 0 Test from group fact failed: (fact 5) Expected: 120 Received: 0 Error: Unit tests failed === License This code is available under the GNU GPLv3 license. === Implementation ==== Runtime code ===== Dependencies <enscript highlight=scheme filename="embedded-test-runtime"> (use format-modular srfi-1) </enscript> ===== Defining tests We use a list to keep track of all tests defined so far: <enscript highlight=scheme filename="embedded-test-runtime"> (define *tests* '()) </enscript> Unit tests are represented as a record: <enscript highlight=scheme filename="embedded-test-runtime"> (define-record test group-name name proc expect equal?) </enscript> The fields serve the following purpose: ; {{group-name}} : If this test belongs to a group of tests, the name of the group. {{#f}} otherwise. ; {{name}} : The name of the test, used for reporting failures. ; {{proc}} : A procedure of one argument that evaluates the test expression. The argument is a procedure that, if called, will cause the test to fail. ; {{expect}} : A procedure of no arguments that evaluates the expected result. ; {{equal?}} : The comparison procedure, receiving the results of calling {{proc}} and {{expect}} and returning a boolean to indicate if they are the same. Now a procedure for registering new tests: <enscript highlight=scheme filename="embedded-test-runtime"> (define (register-test . args) (set! *tests* (cons (apply make-test args) *tests*))) </enscript> ===== Running tests We create an internal procedure to run one test and return success or failure. <enscript highlight=scheme filename="embedded-test-runtime"> (define (run-test test) (when (getenv "TESTS_VERBOSE") (format (current-error-port) "Test~A: ~S..." (if (test-group-name test) (format #f " from group ~A" (test-group-name test)) "") (test-name test))) (let* ((expect ((test-expect test))) (force-fail #f) (result (call-with-current-continuation (lambda (return) ((test-proc test) (lambda args (set! force-fail args) (return (not expect))))))) (pass ((test-equal? test) result expect))) (when (getenv "TESTS_VERBOSE") (format (current-error-port) " ~A~%" (if pass "PASS" "FAIL"))) (unless pass (format (current-error-port) "Test~A failed: ~S~%" (if (test-group-name test) (format #f " from group ~A" (test-group-name test)) "") (test-name test)) (if (list? force-fail) (format (current-error-port) "Aborted:~%~{~S~%~}~%" force-fail) (format (current-error-port) "Expected: ~S~%Received: ~S~%~%" expect result))) pass)) </enscript> Based on that, we define the public {{run-tests}} procedure that evaluates all tests in the order in which they were defined and, if one or more fail, raises an error. <enscript highlight=scheme filename="embedded-test-runtime"> (define (run-tests) (when (getenv "TESTS_SHOW_GROUPS") (format (current-error-port) "Groups:~{ ~A~}~%" (delete-duplicates (map test-group-name (reverse *tests*))))) (when (and (getenv "TESTS") (positive? (count (complement run-test) (reverse (filter (let ((groups (map string->symbol (string-split (or (getenv "TESTS_GROUPS") "") " ")))) (if (null? groups) identity (compose (cut member <> groups) test-group-name))) *tests*))))) (error "Unit tests failed"))) </enscript> ==== Macros All that remains is creating the {{test}} and {{test-group}} macros. The former simply fills in the blanks and calls {{register-test}}. Note that the users should never pass the {{name}} or {{group}} parameters but let it be filled for them. <enscript highlight=scheme filename="embedded-test"> (define-macro (test expr . rest) (let-optionals rest ((expect #t) (cmp? (if (null? rest) '(lambda (a b) a) 'equal?)) (name expr) (group #f)) `(register-test ',group ',name (lambda (test-fail) ,expr) (lambda () ,expect) ,cmp?))) </enscript> The {{test-group}} macro simply modifies the internal {{(test ...)}} forms, filling the group, and returns them. <enscript highlight=scheme filename="embedded-test"> (define-macro (test-group group-name . rest) `(begin ,@(map (lambda (test) (let-optionals (cddr test) ((expect #t) (cmp? (if (null? rest) '(lambda (a b) a) 'equal?)) (name (cadr test))) `(test ,(cadr test) ,expect ,cmp? ,name ,group-name))) rest))) </enscript> === Author This was created by [[weblogs/azul|Alejandro Forero Cuervo]].
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you subtract 24 from 18?