Common Lisp简易单元测试

Practical Common Lisp 一书在探讨Lisp单元测试时,作者 Peter Seibel 使用宏以及嵌套宏调用,让读者深入理解Lisp宏的功用和Lisp单元测试基本方法。宏是Lisp中最强大的部份之一,用于生成Lisp自身代码;而单元测试则是构建大型软件必备的技术。

书中,Peter Seibel 并没有使用单元测试框架,而是从基本原理出发,向读者展示如何使用宏进行优雅的单元测试。

有时候单元测试是极其繁琐和枯燥的,如果合理地使用代码重用技术,将会使单元测试成为简单的工作。Lisp宏在单元测试中是极其有用的(在其他方面也是如此),可用于生成多个含有重复代码的测试用例;在显示测试用例测试结果时,同样可以使用宏来完成重复的工作。

Peter Seibel 为了说明问题,给出一个对内置的 “+” 函数和 “*” 函数进行简单单元测试的示例程序。程序如下:

unit-test.lisp

;;;
;;; Simple unit test in common lisp. (from <Practical Common Lisp> - ch9)
;;;
;;; :last-modified 2018-02-01
;;;

(defvar *test-name* nil "Global. Store test cases' name.")

(defmacro deftest (name parameters &body body)
  "Define a test function. Within a test function we can call
  other test functions or use 'check' to run individual test
  cases."
  `(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."
  `(combine-results
    ,@(loop for f in forms collect `(report-result ,f ',f))))

(defmacro with-gensyms ((&rest names) &body body)
  "Bind each named variable to a fresh, uninterned symbol. The
  functionality is similar to progn."
  `(let ,(loop for n in names collect `(,n (gensym)))
    ,@body))

(defmacro combine-results (&body forms)
  "Combine the results (as booleans) of evaluation 'forms' in order."
  (with-gensyms (result)
    `(let ((,result t))
      ,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
      ,result)))

(defun report-result (result form)
  "Report the results of a single test case. Called by 'check'."
  (format t "~:[FAIL~;pass~] ... ~a: ~a~%" result *test-name* form)
  result)

;;; Test cases
;; level 3
(deftest test-+ ()
  (check
    (= (+ 1 2) 3)
    (= (+ 1 2 3) 6)
    (= (+ -1 -3) -4)))

(deftest test-* ()
  (check
    (= (* 2 2) 4)
    (= (* 3 5) 15)))

;; level 2
(deftest test-arithmetic ()
  ;(combine-results
  (progn
    (test-+)
    (test-*)))

;; level 1
(deftest test-math ()
  (test-arithmetic))

;; test
(test-math)

执行以下命令:

$ clisp -x '(load "unit-test.lisp")'

测试结果自然是两个测试用例均正确:

;; Loading file unit-test.lisp ...
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2) 3)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ 1 2 3) 6)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-+): (= (+ -1 -3) -4)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-*): (= (* 2 2) 4)
pass ... (TEST-MATH TEST-ARITHMETIC TEST-*): (= (* 3 5) 15)
;; Loaded file unit-test.lisp

参考:

  1. Practical Common Lisp. Peter Seibel
  2. http://jtra.cz/stuff/lisp/sclr/

作者: V

Web Dev

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s