;;; File: ;;; unit-test.ss ;;; Authors: ;;; Janet L. N. Davis ;;; Samuel A. Rebelsky ;;; Version: ;;;; 0.5.1 of 24 September 2006 ;;; Summary: ;;; A set of procedures that support a simple form of unit testing ;;; for Scheme code. ;;; Contents: ;;; (begin-tests!) - Set up the environment for testing ;;; (test! expression value) - Determine whether (a) expression can ;;; be evaluated and (b) the value of expression is value. ;;; (test-error! expression) - Verify that expression is ;;; erroneous (that is, that it cannot be evaluated). ;;; (end-tests!) - Conclude a set of tests and report their results. ;;; Use: ;;; In general, you begin a series of tests by calling (begin-tests!). ;;; You conduct each test with the test! procedure, which takes two ;;; parameters: (1) a list that represents an expression to be evaluated ;;; (2) the expected value of that expression. ;;; Use test-error! to evaluate expressions that should result in an error ;;; (e.g., if a precondition is violated). ;;; When you are done, you end the series of tests by calling (end-tests!). ;;; This procedure will report on all the failures. ;;; Practica: ;;; (begin-tests!) ;;; (test! '(reverse (list 'a 'b 'c)) (list 'c 'b 'a)) ;;; (test-error! '(reverse 'spam)) ;;; (end-tests!) ;;; History: ;;; At end. ; +-----------+------------------------------------------------------------ ; | Variables | ; +-----------+ ;;; Named Value: ;;; tests ;;; Type: ;;; integer ;;; Summary: ;;; A count of the tests conducted. (define tests 0) ;;; Named Value: ;;; failures ;;; Type: ;;; List of lists. ;;; Summary: ;;; A list of the failures encountered, in reverse order. An error ;;; is represented with the following form ;;; (EXPRESSION EXPECTED-VALUE ACTUAL-VALUE) (define failures null) ;;; Named Value: ;;; errors ;;; Type: ;;; List of lists. ;;; Summary: ;;; A list of the errors encountered, in reverse order. An error ;;; is represented with the following form ;;; (EXPRESSION EXCEPTION) (define errors null) ; +--------------------+--------------------------------------------------- ; | Primary Procedures | ; +--------------------+ ;;; Procedure: ;;; begin-tests! ;;; Parameters: ;;; (none) ;;; Purpose: ;;; Prepare the system for testing. ;;; Produces: ;;; Nothing. ;;; Preconditions: ;;; (none) ;;; Postconditions: ;;; The system is now in a state that permits use of the testing procedures. (define begin-tests! (lambda () (set! failures null) (set! errors null) (set! tests 0))) ;;; Procedure: ;;; test! ;;; Parameters: ;;; exp, a Scheme value that represents an expression to be evaluated. ;;; expected, a Scheme value that represents the expected result ;;; Purpose: ;;; Evaluate exp and determine whether or not it equals expected. In ;;; either case, updates our testing statistics. ;;; Produces: ;;; (nothing) ;;; Preconditions: ;;; The test system has been initialized with begin-tests! ;;; The test system has not been finished with end-tests! ;;; Postconditions: ;;; The information in the test system has been updated appropriately. (define test! (lambda (exp expected) (set! tests (+ tests 1)) (with-handlers ((exn:fail? (lambda (exception) (set! errors (cons (list exp exception) errors))))) (let ((result (eval exp))) (if (not (equal? result expected)) (set! failures (cons (list exp expected result) failures))))))) ;;; Procedure: ;;; test-error! ;;; Parameters: ;;; exp, a Scheme value that represents an expression to be evaluated. ;;; The expression should be one that is expected to generate an error. ;;; Purpose: ;;; Evaluate exp and determine whether it generates an error. ;;; If there is an error, the test succeeds. ;;; Otherwise, the test fails. ;;; In either case, updates our testing statistics. ;;; Produces: ;;; (nothing) ;;; Preconditions: ;;; The test system has been initialized with begin-tests! ;;; The test system has not been finished with end-tests! ;;; Postconditions: ;;; The information in the test system has been updated appropriately. (define test-error! (lambda (exp) (set! tests (+ tests 1)) (with-handlers ((exn:fail? (lambda (exception) ; This is what we expected! #t ))) (let ((result (eval exp))) ((set! failures (cons (list exp "an error" result) failures))))))) ;;; Procedure: ;;; end-tests! ;;; Parameters: ;;; (none) ;;; Purpose: ;;; Completes a sequence of tests. ;;; Produces: ;;; (nothing) [displays results] ;;; Preconditions: ;;; The test system has been initialized with begin-tests! ;;; The test system has not been finished with end-tests! ;;; Postconditions: ;;; Information about the tests has been displayed. (define end-tests! (lambda () (let* ((num-errors (length errors)) (num-failures (length failures)) (total-failures (+ num-errors num-failures))) (display tests) (display " tests conducted.") (newline) (cond ((= total-failures 0) (display "No tests failed.")) ((= total-failures 1) (display "One test failed.")) (else (display total-failures) (display " tests failed."))) (newline) (cond ((= total-failures 0)) ((= num-errors 0) (display "No errors encountered.") (newline)) ((= num-errors 1) (display "One error encountered:") (newline)) (else (display num-errors) (display " errors encountered:") (newline))) (report-errors) (cond ((= total-failures 0)) ((= num-failures 0) (display "No other tests failed to give the expected result.") (newline)) ((= num-failures 1) (display "One other test failed to give the expected result:") (newline)) (else (display num-failures) (display " other tests failed to give the expected result:") (newline))) (report-failures) (if (= 0 (+ num-failures num-errors)) (display "CONGRATULATIONS! All tests passed.") (display "Sorry. You'll need to fix your code.")) (newline)))) ; +-----------------+------------------------------------------------------ ; | Local Utilities | ; +-----------------+ (define report-failures (lambda () (report-failures-helper (reverse failures)))) (define report-failures-helper (lambda (remaining-failures) (if (not (null? remaining-failures)) (let ((error (car remaining-failures))) (display " For ") (write (car error)) (display " expected [") (write (cadr error)) (display "] got [") (write (caddr error)) (display "]") (newline) (report-failures-helper (cdr remaining-failures)))))) (define report-errors (lambda () (report-errors-helper (reverse errors)))) (define report-errors-helper (lambda (remaining-errors) (if (not (null? remaining-errors)) (let ((exception (car remaining-errors))) (display " The expression ") (display (car exception)) (display " failed to evaluate because [") (display (exn-message (cadr exception))) (display "]") (newline) (report-errors-helper (cdr remaining-errors)))))) ; +---------+-------------------------------------------------------------- ; | History | ; +---------+ ; Friday, 22 September 2006 (v 0.1) [Janet L. N. Davis and Samuel A. Rebelsky] ; * Created. Did not yet handle errors. ; Friday, 22 September 2006 (v 0.2) [Janet L. N. Davis and Samuel A. Rebelsky] ; * Added error handling code. ; * Added documentation. ; * Moved tests of the testing code to a separate file. ; * Renamed three core procedures. ; Friday, 22 September 2006 (v 0.3) [Janet L. N. Davis] ; * Added test-error! procedure. ; * Modified test! to use the exn:fail? procedure to test for an exception. ; (This procedure is recommended as it does not catch the break exception.) ; * Fixed bug that caused report-failures-helper and report-errors-helper ; to infinite-loop if there were multiple failures or errors. ; Friday, 22 September 2006 (v 0.4) [Samuel A. Rebelsky] ; * Replaced calls to "display" with calls to "write" for the expressions. ; (Otherwise, the output is not quite so clear.) ; * Updated the final report for clarity (now reports total failures, errors, ; and non-error failures). ; Sunday, 24 September 2006 (v 0.5) [Janet L. N. Davis] ; * Remembered to set errors to null in begin-tests! ; * Fixed bug in reporting the total number of errors. ; * Updated wording for test failures that are not unexpected errors. ; * Averted printing of superfluous newlines when all tests succeed. ; * Updated front matter. ; Sunday, 254 September 2006 (v 0.5.1) [Samuel A. Rebelsky] ; * Updated the message for tests that are not errors. ; * Updated the contents to give shor tdefinitions. ; +-------+---------------------------------------------------------------- ; | To Do | ; +-------+ ; * Document helper procedures. ; * Find a way to get rid of the return value from test-error!