#:g1: lisp-unit

Posted 2012-12-04 15:00:00 GMT

この投稿はCommon Lisp Libraries Advent Calendar 2012の5日目の記事です。
約5年前にlisp-unitを試してみた簡単な紹介記事を書いたのですが、5年経過した今、再度検証しつつアップデートしてみました。
主にアップデートにあたって私がユニットテストの実際的な利用において重要だと考えているQuicklisp(ASDF)との連携について追記しています。テストの実行が簡単にできないと、テストも放置しがちになるので、どんなユニットテストを使うかも大事ですが、簡単に実行できるようにしておくのも大事かなと思います。

lisp-unitとは

5年前は、Chris Riesbeck氏の書いたオリジナルが流通していましたが、現在Quicklispで入手できるものは、github上でメンテナンスされている様子。Quicklispのダウンロードの統計によれば、現在一番良くダウンロードされているユニットテストのようです。
オリジナルからは細部が若干変更されているようです。

パッケージ名lisp-unit
本拠地サイトOdonataResearchLLC / lisp-unit
オリジナル本拠地サイトlisp-unit
ドキュメントlisp-unit: Reference
Clikihttp://www.cliki.net/lisp-unit
Quicklisp

インストール

quicklispで可能なので、(ql:quickload :lisp-unit)で一発インストール完了です。

使ってみる

かなり古くからある、リグレッションテストのパッケージ:RTと大体同じような使い勝手に思えました。テストをDEFINE-TESTで定義して、RUN-TESTで実行するところなど共通点は多いと思います。
とりあえず、適当なプロジェクトを作ったとします
;;;; package.lisp

(cl:in-package :cl-user)

(defpackage :clladvcal-lisp-unit (:use :cl :lisp-unit) (:export))

;;;; clladvcal-lisp-unit.lisp

(cl:in-package :clladvcal-lisp-unit)

(defun fib (n) (check-type n (integer 0 *)) (cond ((< n 2) n) ('T (+ (fib (1- n)) (fib (- n 2))))))

(defun fib-iter (n) (check-type n (integer 0 *)) (do ((cnt n (1- cnt)) (a1 1 (+ a1 a2)) (a2 0 a1)) ((< cnt 2) a1)))

これらにテストを加える場合、同じファイルに書いてもOKですし、別にしてもOKです。今回の例では、とりあえず別ファイルに分けるとします。
;;; test.lisp
(cl:in-package :clladvcal-lisp-unit)

(define-test fib ;; test[1] (assert-eql 55 (fib 10))

;; test[2] (assert-eql 1 (fib 1))

;; test[3..32] ;; (fib 1..30)と(fib-iter 1..30)は同じ値である筈 (loop :for i :from 1 :to 30 :do (assert-eql (fib-iter i) (fib i))))

(define-test fib-errors ;; 引数の型チェック (dolist (fn (list #'fib #'fib-iter)) (dolist (arg '(a -1 1.0)) (assert-error 'type-error (funcall fn arg)))))

定義したテストは対話的に確認することができます。
(list-tests)
;=>  (FIB FIB-ERRORS)
定義した二つのテストが見えますテストの実行は、テスト名を指定したり全部を一気に実行できたりします。下記では、:allを指定することによりパッケージ内の定義を全部実行しています。パッケージを指定できるのが便利ですね。
(run-tests :all :clladvcal-lisp-unit)

;>> Unit Test Summary ;>> | 38 assertions total ;>> | 38 passed ;>> | 0 failed ;>> | 0 execution errors ;>> | 0 missing tests ;>> ;>> ;=> #<TEST-RESULTS Total(38) Passed(38) Failed(0) Errors(0)> ;

RTでは、deftestに一つずつ別の名前を付けなくちゃいけなかったりするので、自分は、それ用のマクロを定義して使ったりしてましたが、lisp-unitはRTより使い勝手は良いようです。

ASDF/Quicklispとの連携

ASDFにはtest-opというものがあり、これを定義すると
(asdf:test-system :clladvcal-lisp-unit)
でテストが実行できるので便利です。test-opを定義しておけば、SLIMEからもslime-repl-test-systemで実行できるので是非活用したいところです。
今回のプロジェクトでサンプルを書くなら下記のような感じでしょうか。
;;;; clladvcal-lisp-unit.asd -*- Mode: Lisp;-*- 

(cl:in-package :asdf)

(defsystem :clladvcal-lisp-unit :serial t :depends-on (:lisp-unit) :components ((:file "package") (:file "clladvcal-lisp-unit") (:file "test")))

(macrolet ((_ (sym &rest args) `(,(intern (symbol-name sym) (find-package :lisp-unit)) ,@args))) (defmethod perform ((o test-op) (c (eql (find-system :clladvcal-lisp-unit)))) (load-system :clladvcal-lisp-unit) ;; (let ((test (_ run-tests :all :clladvcal-lisp-unit))) (when (or (_ failed-tests test) (_ error-tests test)) (error "test-op failed")))))

こういうファイルを雛形として、Quickproject等でプロジェクト作成時に生成させると非常に便利です。

まとめ

ということで今回は、lisp-unitを紹介してみました。気付けばアップデートした内容の方が多く、一から書いたのとあまり変わらない感じになってしまいました。ちなみにlisp-unitを紹介しておいてなんですが、私は、fiveamを愛用しています。

comments powered by Disqus