Emacs Lisp Expectationsで超簡単ユニットテスト in elisp

elispにもRubyのexpectationsのような美しく記述できるテスティングフレームワークが欲しい - http://rubikitch.com/に移転しました

expectationsに激萌えの俺がさくっと作ったよ、expectations for elisp。expectations.elじゃ検索しづらいと思ってel-expectations.elにしといた。

こんな文法でテストが書ける。本体の評価結果が期待値とマッチすればテストが通る。本家expectationsにない機能として「desc」でコメントが書けること。ただし、たんなるデリミタなのでテスト名ではないw

(expectations
  (desc コメント)
  (expect 期待値
    本体)
  (expect ...)
  ...
  )

具体的なコードはこんなの。

(expectations
   (desc "simple expectation")
   (expect 3           ; (+ 1 2) が 3 になるべき。
     (+ 1 2))
   (expect "hoge"      ; (concat "ho" "ge") が "hoge" になるべき。
     (concat "ho" "ge"))
   (expect "fuga"      ; 〜の評価結果が "fuga" になるべき。
     (set-buffer (get-buffer-create "tmp"))
     (erase-buffer)
     (insert "fuga")
     (buffer-string))

   (desc "extended expectation")
   (expect (buffer "*scratch*")  ; #<buffer *scratch*> になるべき。
     (with-current-buffer "*scratch*"
       (current-buffer)))
   (expect (regexp "o")          ; "hoge" =~ /o/ であるべき。
     "hoge")
   (expect (type integer)        ; 3 は整数であるべき。
     3)

   (desc "error expectation")
   (expect (error arith-error)   ; 0除算はarith-errorが起きるべき。
     (/ 1 0))
   (expect (error)               ; 0除算は何でもいいからエラーであるべき。
     (/ 1 0)))

どう、簡単でしょ?

elispのテスト書くのめんどくせえと思ってる人も試してみる価値はある、断言しよう。

インストール

M-x install-elisp-from-emacswiki el-expectations.el

EmacsWikiからもってけ!ブラウザで見るには↓へどうぞ。

http://www.emacswiki.org/cgi-bin/wiki/download/el-expectations.el

使い方

  • expectationsのS式をM-C-xとかでeval。
  • M-x expectations-execute でテスト実行。←頻繁に実行するならキーバインドしとくとよい。
  • 失敗したテストがあったら C-x ` (next-error)やprevious-errorで辿る。

ただ、現時点ではひとつのファイルにふたつ以上のexpectationsのS式は書けない制限がある。ふたつ以上書いてしまうとC-x `でコケてしまう。この制限はなくすべきだな…

成功時の実行結果

↑の例を実行した結果は *expectations result* バッファにこんな風に出る。

Executing expectations in nil...
1  :============ simple expectation ============
2  :OK
3  :OK
4  :OK
5  :=========== extended expectation ===========
6  :OK
7  :OK
8  :OK
9  :============ error expectation ============
10 :OK
11 :OK

失敗時の実行結果

成功時じゃつまらんので失敗時の結果も貼っとく。

(expectations
  (desc "error test")
  (expect 4
    (error "hoge")
    4)

  (desc "eval")
  (expect 5 4)
  (expect "hoge" "hage")
  (expect '(1) '(2))
  (expect (get-buffer-create "buf1") (get-buffer-create "buf2"))

  (desc "buffer")
  (expect (buffer "*scratch*")
    (get-buffer-create "*fail*"))

  (desc "regexp")
  (expect (regexp "o")
    "hage")

  (desc "type")
  (expect (type string)
    1)
  (desc "error")
  (expect (error arith-error)
    (/ 1 1))
  (expect (error end-of-file)
    (/ 1 0))
  (expect (error)
    (/ 1 1))
  )

これを実行してみるとこうなる。

Executing expectations in /m/home/rubikitch/emacs/lisp/el-expectations-failure-sample.el...
1  :================ error test ================
2  :ERROR: (error hoge)
3  :=================== eval ===================
4  :FAIL: Expected <5> but was <4>
5  :FAIL: Expected <"hoge"> but was <"hage">
6  :FAIL: Expected <(1)> but was <(2)>
7  :FAIL: Expected <#<buffer buf1>> but was <#<buffer buf2>>
8  :================== buffer ==================
9  :FAIL: Expected <#<buffer *scratch*>> but was <#<buffer *fail*>>
10 :================== regexp ==================
11 :FAIL: "hage" should match /o/
12 :=================== type ===================
13 :FAIL: 1 is not a string
14 :================== error ==================
15 :FAIL: should raise <arith-error>, but no error was raised
16 :FAIL: should raise <end-of-file>, but raised <arith-error>
17 :FAIL: should raise any error, but no error was raised

追記:バッチモード

他言語のテスティングフレームワークのようにコマンドラインからも実行できるようにしといた。
以下のシェルスクリプトをel-expectationsという名前で保存する。

バッチモードで実行することで、ライブラリのrequire忘れとかのミスを未然に防ぐことができる。Emacsの中だとrequireされている状態なのでついついrequireを入れるのを忘れてしまうので。それと、.emacsに影響されないので設定依存になっていないことも確かめられる。

#!/bin/sh
EMACS=emacs
OPTIONS="-L . -L $HOME/emacs/lisp"
OUTPUT=/tmp/.el-expectations

$EMACS -q --no-site-file --batch $OPTIONS -l el-expectations -f batch-expectations $1 $OUTPUT
ret=$?
cat $OUTPUT
rm $OUTPUT
exit $ret

でもって、おもむろに

$ el-expectations el-expectations-success-sample.el
Loading subst-ksc...
Loading subst-gb2312...
Loading subst-big5...
Loading subst-jis...
Executing expectations in /m/home/rubikitch/emacs/lisp/el-expectations-success-sample.el...
1  :========== run-hook-with-args-until-success ==========
2  :OK
3  :OK
4  :OK
5  :======================= buffer =======================
6  :OK
7  :OK
8  :======================= regexp =======================
9  :OK
10 :======================== type ========================
11 :OK
12 :OK
13 :======================= error =======================
14 :OK
15 :OK
16 :======================== eval ========================
17 :OK
18 :OK

12 expectations, 0 failures, 0 errors
$ el-expectations el-expectations-failure-sample.el
Loading subst-ksc...
Loading subst-gb2312...
Loading subst-big5...
Loading subst-jis...
Executing expectations in /m/home/rubikitch/emacs/lisp/el-expectations-failure-sample.el...
1  :===================== error test =====================
2  :ERROR: (error hoge)
3  :======================== eval ========================
4  :FAIL: Expected <5> but was <4>
5  :FAIL: Expected <"hoge"> but was <"hage">
6  :FAIL: Expected <(1)> but was <(2)>
7  :FAIL: Expected <#<buffer buf1>> but was <#<buffer buf2>>
8  :======================= buffer =======================
9  :FAIL: Expected <#<buffer *scratch*>> but was <#<buffer *fail*>>
10 :======================= regexp =======================
11 :FAIL: "hage" should match /o/
12 :======================== type ========================
13 :FAIL: 1 is not a string
14 :======================= error =======================
15 :FAIL: should raise <arith-error>, but no error was raised
16 :FAIL: should raise <end-of-file>, but raised <arith-error>
17 :FAIL: should raise any error, but no error was raised

11 expectations, 10 failures, 1 errors
zsh: exit 11    el-expectations el-expectations-failure-sample.el