ertを使ってelispのunittestを書く方法のメモ

ertを使う。標準で添付されているのでインストールは不要(なはず)。

使いかた

以下のような関数を定義しておく(テスト対象)

(defun my:add (x y) (+ x y))

テストを書く。

(require 'ert)

(ert-deftest add20 ()
  (should (= 20 (my:add 10 10)))
  )

テストを実行する

  1. (M-x eval-buffer)
  2. M-x ert

ertのdefaultのtは全てのテストを実行するという意味。勝手にテストを読み込んでくれないので書いたテストを読み込んであげる必要がある(eval-buffer)。

実行結果は以下のようなもの

Selector: t
Passed:  1
Failed:  0
Skipped: 0
Total:   1/1

Started at:   2019-01-07 05:16:12+0900
Finished.
Finished at:  2019-01-07 05:16:12+0900

.

失敗した場合

以下のようなテストを追加してみる。これは絶対失敗するもの。

(ert-deftest add21 ()
  (should (= 20 (my:add 10 10)))
  )

再度ertを実行(M-x ert)

Selector: t
Passed:  1
Failed:  1 (1 unexpected)
Skipped: 0
Total:   2/2

Started at:   2019-01-07 05:14:03+0900
Finished.
Finished at:  2019-01-07 05:14:03+0900

.F

F add21
    (ert-test-failed
     ((should
       (= 21
          (my:add 10 10)))
      :form
      (= 21 20)
      :value nil))

テストの書き方

基本的にはert-deftestのマクロの中で以下を利用してassertionを書く。

  • should
  • should-not
  • should-error
  • should-not-err

あと、環境によってスキップしたいものなどは以下を使ってスキップできる。

  • skip-unless

例えば、executable-findでpylosが存在しない場合にスキップしたい場合だとか。

  (skip-unless (executable-find "pyls"))

batchで実行(CIなどに)

てきとうにmakefileを書いた。同階層のディレクトリの*test.elがテストとして認識される。何か依存しているライブラリがあった場合には-l subrを参考に追加する。

EMACS ?= emacs
SELECTOR ?= t

TESTS ?= $(wildcard *test.el)

test:
  $(EMACS) -Q --batch  -L . \
      -l subr \
      $(addprefix -l ,$(TESTS)) \
      --eval '(setq ert-batch-backtrace-right-margin 100)' \
      --eval '(ert-run-tests-batch-and-exit (quote $(SELECTOR)))'

試せるgist

けっこう出力がうるさいのが不便と言えば不便?

$ emacs -Q --batch  -L . \
    -l subr \
    -l 00test.el \
    --eval '(setq ert-batch-backtrace-right-margin 100)' \
    --eval '(ert-run-tests-batch-and-exit (quote t))'
Running 2 tests (2019-01-07 05:22:33+0900)
   passed  1/2  add20
Test add21 backtrace:
  signal(ert-test-failed (((should (= 21 (my:add 10 10))) :form (= 21 20) :value nil)))
  ert-fail(((should (= 21 (my:add 10 10))) :form (= 21 20) :value nil))
  (if (unwind-protect (setq value-7 (apply fn-5 args-6)) (setq form-description-9 (nconc (list '(sho
  (let (form-description-9) (if (unwind-protect (setq value-7 (apply fn-5 args-6)) (setq form-descri
  (let ((value-7 'ert-form-evaluation-aborted-8)) (let (form-description-9) (if (unwind-protect (set
  (let* ((fn-5 (function =)) (args-6 (condition-case err (let ((signal-hook-function (function ert--
  (lambda nil (let* ((fn-5 (function =)) (args-6 (condition-case err (let ((signal-hook-function (fu
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test :name add21 :documentation ni
  ert-run-test(#s(ert-test :name add21 :documentation nil :body (lambda nil (let* ((fn-5 (function =
  ert-run-or-rerun-test(#s(ert--stats :selector t :tests [#s(ert-test :name add20 :documentation nil
  ert-run-tests(t #f(compiled-function (event-type &rest event-args) #<bytecode 0x4883c9>) nil)
  ert-run-tests-batch(t)
  ert-run-tests-batch-and-exit(t)
  eval((ert-run-tests-batch-and-exit 't))
  command-line-1(("-L" "." "-l" "subr" "-l" "00test.el" "--eval" "(setq ert-batch-backtrace-right-ma
  command-line()
  normal-top-level()
Test add21 condition:
    (ert-test-failed
     ((should
       (= 21
      (my:add 10 10)))
      :form
      (= 21 20)
      :value nil))
   FAILED  2/2  add21

Ran 2 tests, 1 results as expected, 1 unexpected (2019-01-07 05:22:33+0900)

1 unexpected results:
   FAILED  add21