Presentation is loading. Please wait.

Presentation is loading. Please wait.

PRACTICAL COMMON LISP Peter Seibel 1.

Similar presentations


Presentation on theme: "PRACTICAL COMMON LISP Peter Seibel 1."— Presentation transcript:

1 PRACTICAL COMMON LISP Peter Seibel http://www.gigamonkeys.com/book/ 1

2 CHAPTER 9 PRACTICAL: BUILDING A UNIT TEST FRAMEWORK 2

3 TWO FIRST TRIES The key feature of an automated testing framework is that the framework is responsible for telling you whether all the tests passed. Each test case must be an expression that yields a boolean value—true or false, pass or fail. For instance, if you were writing tests for the built-in + function, these might be reasonable test cases: (= (+ 1 2) 3) (= (+ 1 2 3) 6) (= (+ -1 -3) -4) Now, we can just write a function that evaluates the test cases and ANDs the results together. (defun test-+ () (and (= (+ 1 2) 3) (= (+ 1 2 3) 6) (= (+ -1 -3) -4))) Whenever you want to run this set of test cases, you can call test-+. CL-USER> (test-+) T 3

4 TWO FIRST TRIES However, once a test case fails, we'll know something failed, but we'll have no idea which test case it was. So let's try another simple approach to find out what happens to each test case. (defun test-+ () (format t "~:[FAIL~;pass~]... ~a~%" (= (+ 1 2) 3) '(= (+ 1 2) 3)) (format t "~:[FAIL~;pass~]... ~a~%" (= (+ 1 2 3) 6) '(= (+ 1 2 3) 6)) (format t "~:[FAIL~;pass~]... ~a~%" (= (+ -1 -3) -4) '(= (+ -1 -3) -5))) The ~:[FAIL~;pass~] part of the FORMAT directive causes FORMAT to print “FAIL” if the first format argument is false and “pass” otherwise. CL-USER> (test-+) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) pass... (= (+ -1 -3) -4) NIL 4

5 REFACTORING The repeated calls to FORMAT should be refactored( 重構 ). The simplest way to get rid of the repeated similar calls to FORMAT is to create a new function. (defun report-result (result form) (format t "~:[FAIL~;pass~]... ~a~%" result form)) Now we can write test-+ with calls to report-result instead of FORMAT. (defun test-+ () (report-result (= (+ 1 2) 3) '(= (+ 1 2) 3)) (report-result (= (+ 1 2 3) 6) '(= (+ 1 2 3) 6)) (report-result (= (+ -1 -3) -4) '(= (+ -1 -3) -4))) Refactoring( 重構代碼 ): 指在不改變代碼的外部行為情況下修改原始碼 5

6 REFACTORING Next we need to get rid of the duplication of the test case expression, with its attendant risk of mislabeling of results. (defmacro check (form) ` (report-result,form ',form)) For example, (check (= (+ 1 2) 3)) means (report-result (= (+ 1 2) 3) '(= (+ 1 2) 3)) Now we can change test-+ to use check. (defun test-+ () (check (= (+ 1 2) 3)) (check (= (+ 1 2 3) 6)) (check (= (+ -1 -3) -4))) 6

7 REFACTORING We can define check to take an arbitrary number of forms and wrap them each in a call to report-result. (defmacro check (&body forms) `(progn,@(loop for f in forms collect `(report-result,f ',f)))) This definition uses a common macro idiom of wrapping a PROGN around a series of forms in order to turn them into a single form. Notice,@ to splice in the result of an expression that returns a list of expressions that are themselves generated with a backquote template. With the new version of check we can write a new version of test-+ like this: (defun test-+ () (check (= (+ 1 2) 3) (= (+ 1 2 3) 6) (= (+ -1 -3) -4))) 7 > (test-+) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) pass... (= (+ -1 -3) -4) NIL

8 FIXING THE RETURN VALUE As a first step, you can make a small change to report-result so it returns the result of the test case it's reporting. (defun report-result (result form) (format t "~:[FAIL~;pass~]... ~a~%" result form) result) > (test-+) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) pass... (= (+ -1 -3) -4) T 8

9 FIXING THE RETURN VALUE We can define a macro combine-results which can work like this: (combine-results (foo) (bar) (baz)) It means that (let ((result t)) (unless (foo) (setf result nil)) (unless (bar) (setf result nil)) (unless (baz) (setf result nil)) result) Now, combine-results can be defined as: (defmacro combine-results (&body forms) (with-gensyms (result) `(let ((,result t)),@(loop for f in forms collect `(unless,f (setf,result nil))),result))) Ps. with-gensyms: http://www.clisp.org/impnotes/macros3.html#with-gensyms 9

10 FIXING THE RETURN VALUE We can fix check by simply changing the expansion to use combine- results instead of PROGN. (defmacro check (&body forms) `(combine-results,@(loop for f in forms collect `(report-result,f ',f)))) CL-USER> (test-+) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) pass... (= (+ -1 -3) -4) T And if you change one of the test cases so it fails, the final return value changes to NIL. CL-USER> (test-+) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) FAIL... (= (+ -1 -3) -5) NIL 10

11 BETTER RESULT REPORTING If we write a lot of tests, we’ll probably want to organize them somehow, rather than shoving( 亂塞 ) them all into one function. For instance, suppose we wanted to add some test cases for the * function. We might write a new test function. (defun test-* () (check (= (* 2 2) 4) (= (* 3 5) 15))) Now that you have two test functions, you’ll probably want another function that runs all the tests. (defun test-arithmetic () (progn (test-+) (test-*))) 11

12 BETTER RESULT REPORTING CL-USER> (test-arithmetic) pass... (= (+ 1 2) 3) pass... (= (+ 1 2 3) 6) pass... (= (+ -1 -3) -4) pass... (= (* 2 2) 4) pass... (= (* 3 5) 15) T Now imagine that one of the test cases failed and we need to track down the problem. With only five test cases and two test functions, it won’t be too hard to find the code of the failing test case. But suppose we had 500 test cases spread across 20 functions. It might be nice if the results told us what function each test case came from. 12

13 BETTER RESULT REPORTING (defvar *test-name* nil) (defun report-result (result form) (format t "~:[FAIL~;pass~]... ~a: ~a~%" result *test-name* form) result) (defmacro check (&body forms) `(progn,@(loop for f in forms collect `(report-result,f ',f)))) (defun test-+ () (let ((*test-name* 'test-+)) (check (= (+ 1 2) 3) (= (+ 1 2 3) 6) (= (+ -1 -3) -4)))) (defun test-* () (let ((*test-name* 'test-*)) (check (= (* 2 2) 4) (= (* 3 5) 15))) (defun test-arithmetic () (progn (test-+) (test-*)))) 13 CL-USER> (test-arithmetic) pass... TEST-+: (= (+ 1 2) 3) pass... TEST-+: (= (+ 1 2 3) 6) pass... TEST-+: (= (+ -1 -3) -4) pass... TEST-*: (= (* 2 2) 4) pass... TEST-*: (= (* 3 5) 15) T


Download ppt "PRACTICAL COMMON LISP Peter Seibel 1."

Similar presentations


Ads by Google