Emacs Lispで書いたスクリプトをert.el
でユニットテストしてみました。
Emacs Lispでも、少し込み入ったスクリプトを書いていると、
ユニットテストをしたくなることがあります。
少し調べたところert.el
というライブラリが見つかり、使いやすく便利
だったのでご紹介したいと思います。
(なお、この記事の内容はDebian 5 (lenny)上のEmacs 23に基づいています。)
ert.el
ert.el(Emacs Lisp Regression Testing)は、いくつかあるEmacs Lisp向け ユニットテスティングツールのひとつです。Emacs trunkに入ったそうなので、 そのうち標準で使えるようになるはずです。さしあたって単体で手に入れる こともできます。
対話的にはもちろん、非対話的にも実行できるのが便利なところです。
ert.elの簡単な使い方
いくつかの使い方がありますが,次のような構造を持ったテストスクリプトを 書いて、Emacsのバッチモードで実行する方法が分かりやすいでしょう。
;; test-foo.el
;; 先頭でertとテスト対象をロードしておく
(require 'ert)
(require 'TEST-TARGET)
;; ert-deftestでテストを定義する
(ert-deftest TESTNAME ()
(should (equal EXPECTED
ACTUAL)))
;; 末尾にバッチ処理の指示を書いておく
(ert-run-tests-batch-and-exit)
単純な例を書いてみます。
テスト対象のスクリプト(my-fold.el
)は次のようなものだとします。
;; my-fold.el
(defun my-foldl (f seed ls)
(mapc (lambda (x) (setq seed (funcall f x seed))) ls)
seed)
(defun my-foldr (f seed ls)
(my-foldl f seed (reverse ls)))
(provide 'my-fold)
テストスクリプト(test-my-fold.el
)は次のような内容にします。
;; test-my-fold.el
(require 'ert)
(require 'my-fold)
(ert-deftest test-my-foldl ()
(should (equal '(3 2 1)
(my-foldl 'cons '() '(1 2 3)))))
(ert-deftest test-my-foldr ()
(should (equal '(1 2 3)
(my-foldr 'cons '() '(1 2 3)))))
(ert-run-tests-batch-and-exit)
これらの2つのファイルをカレントディレクトリに置いて、
コマンドラインから次のようにemacs
を実行します。
$ emacs --directory . --batch --quick --eval '(load-file "test-my-fold.el")'
次のように表示されればテスト成功です。
Loading /path/to/test-my-fold.el (source)...
Running 2 tests (2011-04-11 xx:xx:xx)
passed 1/2 test-my-foldl
passed 2/2 test-my-foldr
Ran 2 tests, 2 results as expected (2011-04-11 xx:xx:xx)
すべてのテストについてpassed
と表示されていますね。
一方、テストが失敗したときは、失敗の内容を教えてくれるメッセージが
表示されます。
試しに、my-fold.el
の中のmy-foldr
関数の2行目を、次のように変更して
わざと間違った値を返すようにしてみます。
(defun my-foldr (f seed ls)
- (my-foldl f seed (reverse ls)))
+ (my-foldl f seed ls))
この状態でテストを実行すると、次のようなメッセージが表示されます。
Loading /path/to/test-my-fold.el (source)...
Running 2 tests (2011-04-11 xx:xx:xx)
passed 1/2 test-my-foldl
Test test-my-foldr backtrace:
...(省略)...
Test test-my-foldr condition:
(ert-test-failed
((should
(equal '...
(my-foldr ... ... ...)))
:form
(equal
(1 2 3)
(3 2 1))
:value nil :explanation
(list-elt 0
(different-atoms
(1 "#x1" "?")
(3 "#x3" "?")))))
FAILED 2/2 test-my-foldr
Ran 2 tests, 1 results as expected, 1 unexpected (2011-04-11 xx:xx:xx)
1 unexpected results:
FAILED test-my-foldr
メッセージの末尾にFAILED test-my-foldr
とあることから,テストのうち
test-my-foldr
が失敗したことが分かります。
さかのぼってtest-my-foldr
の:form
や:explanation
のあたりを見ると、
(1 2 3)
を期待したのに(3 2 1)
が返ってきているらしいことが分かります。
こういった情報を手がかりに、テスト対象やテストスクリプトを直したり発展 させたりしていけばよいわけです。
Makefileの例
先の例では、話を簡単にするために、テストスクリプトをコマンドラインから 直接指定して実行しましたが、実際にはもう少し楽をしたいところです。 テストの実行を効率化するためのMakefile(GNU Make)の例を書いておきます (行頭の空白は、適宜TAB文字に置き換えてください)。
#!/usr/bin/make
EMACS = emacs --batch --quick --directory .
WGET = wget --timestamping
SRC = my-fold.el
cached = ert.el
clean += $(cached)
testlogs = $(foreach f,$(SRC),$(f:%.el=test-%-el.log))
mostlyclean += $(testlogs)
.PHONY: all test mostlyclean clean
all: test
test: $(testlogs)
test-%-el.log: test-%.el ert.el
$(EMACS) --eval '(load-file "$<")' 2>&1 | tee $@
ert.el:
$(WGET) --output-document=$@ \
http://git.savannah.gnu.org/cgit/emacs.git/plain/lisp/emacs-lisp/ert.el
mostlyclean:
-rm -f $(mostlyclean)
clean: mostlyclean
-rm -f $(clean)
テスト対象のスクリプトは、SRC
にスペース区切りで列挙してください。
テストスクリプトには、test-*.el
というファイル名を付けておいてください。
make test
でテストが実行され、メッセージが画面とログファイル(*.log
)
に出力されます。
$ make test
make mostlyclean
でログファイルが消去されます。
$ make mostlyclean
なお、自動的にダウンロードされるert.el
を消去したい場合は、
make clean
を実行してください。
覚え書き
試している途中でいくつか失敗をしたのでメモしておきます。
-
emacsの
--script
オプションは--directory
オプションよりも優先されるらしい。app.el
とd/lib.el
があって、app.el
で(require 'lib)
しているとする。 このとき、d/lib.el
を使うために$ emacs --directory d --script app.el
とすると、
d/lib.el
の読み込みに失敗してCannot open load file: lib
と 言われる。d
がload-path
に追加される前にapp.el
が読まれるらしい。とりあえず
--batch
と--eval
に変更して回避した。$ emacs --batch --directory . --eval '(load-file "app.el")'
-
テストスクリプト内で、テスト対象よりも先に
ert
をロードしないと エラーが起きることがあった。
参考資料
- EmacsWiki: Unit Testing
Other Articles
- 13 Oct 2017: 『テスト駆動開発』
- 19 Oct 2016: 『新装版 達人プログラマー 職人から名匠への道』
- 19 Aug 2016: 『プログラミングElixir』
- 20 Oct 2015: Migrating from git-media to git-lfs
- 04 Oct 2015: Git Large File Storageクライアントのインストール
- 12 Aug 2015: isbn.rb
- 22 Apr 2015: 「なるのか、なすのか?」(To Be Or To Do?)