備忘録

いろいろ忘れないためのブログ

Emacs Lispのテスト、依存性管理、CI

| Comments

現在開発中のguide-keyの機能がそれなりに増えてきたので、そろそろテストを書きた いなと思いました。そこでERTでユニットテストを書いて、Caskで依存関係を解決して、 Travis CIでCIするところまでできたので紹介します。

ERTでユニットテスト

ERTはEmacs Lisp Regression Testingの略で、Emacs Lispのテスティングツールです。 JUnitなどと同様にユニットテストが書けます。

ディレクトリ構成

が丁度良くテストの最小構成を紹介していたので、真似して以下のようなディレクトリ 構成にしました。

guide-key/
├── guide-key.el
└── test/
    └── guide-key-test.el

参考にしたリポジトリも、概ねリポジトリ直下に test などのテスト専用のディレ クトリを配置しているのが多かったです。

テストを書いて実行

テストの書き方の詳細はマニュアルに譲りますが、 ert-deftest でテストケースを 定義し、 should でアサーションすることができます。とりあえず最近追加した関数 のテストを書いてみました。

(require 'ert)
(require 'guide-key)
(eval-when-compile
  (require 'cl))

(ert-deftest guide-key-test/get-highlight-face ()
  "Test of `guide-key/get-highlight-face'"
  (let ((guide-key/highlight-command-regexp
         '("rectangle"
           ("register" . font-lock-type-face)
           ("bookmark" . font-lock-warning-face)
           ))
        (fixtures
         '(("Prefix Command" . guide-key/prefix-command-face)
           ("string-rectangle" . guide-key/highlight-command-face)
           ("jump-to-register" . font-lock-type-face)
           ("bookmark-jump" . font-lock-warning-face)
           ("copy-rectangle-to-register" . guide-key/highlight-command-face)
           ("<NOTEXIST>" . nil)
           ))
        actual)
    (loop for (input . expected) in fixtures
          do
          (setq actual (guide-key/get-highlight-face input))
          (should (eq actual expected)))
    ))

まだテストの前処理、後処理やテストケースの構造化をする方法がよくわからないの で、識者の意見がほしいです。

テストの実行するにはEmacs内から ert コマンドを実行するか、以下のようにコマン ドラインからEmacsのバッチを呼び出します。

$ emacs -batch -l test/guide-key-test.el -f ert-run-tests-batch-and-exit

しかしguide-keyはpopwinに依存しているため、このコマンドだけではテストが実行で きません。popwinのあるところに load-path を通す必要があります。これを手動で やるのは大変なので、Caskという依存関係を解決してくれるツールを利用することにし ました。

Caskで依存関係管理

CaskはEmacsの依存関係管理ツールです。標準添付のpackage.elもライブラリの依存関 係を考慮して必要ライブラリを一括でダウンロードすることはできます。しかし 個人 の設定のために ~/.emacs.d にダウンロードする前提なので、ライブラリの開発で 利用するのには向きません。

CaskはRubyでいうGemfileのように、Caskファイルに依存しているライブラリを記述す ることで、一括ダウンロードや PATHload-path の調整をしてくれます。

インストール

$ curl -fsSkL https://raw.github.com/cask/cask/master/go | python
$ export PATH="$HOME/.cask/bin:$PATH"

だけです。

ちなみにWindowsで試してみたら、うまくインストール出来ませんでした。Cygwinなの が悪いのでしょうか。

依存関係の解決

リポジトリの直下の Cask ファイルに、依存しているライブラリを記述します。例え ばこのような感じです。

(source gnu)
(source melpa)
(source marmalade)

(package-file "guide-key.el")

(development
 (depends-on "ert")
 (depends-on "popwin"))

source がライブラリを参照する場所、 package-file が開発しているライブラリ、 depends-on が依存しているライブラリです。詳しくはUsageを見てください。 guide-key自身が依存しているのはpopwinだけですが、Emacs23でテストする際にertが 必要になるのでertも依存ライブラリとしています。

実際に依存ライブラリをダウンロードするには、コマンドラインで cask あるいは cask install します。

$ cask install
Contacting host: marmalade-repo.org:80
Saving file /home/kai/.emacs.d/my-lisp/guide-key/.cask/24.3.1/elpa/archives/marmalade/archive-contents...
(中略)
Wrote /home/kai/.emacs.d/my-lisp/guide-key/.cask/24.3.1/elpa/popwin-20130329.435/popwin.elc
Done (Total of 2 files compiled, 1 skipped)
$ ls .cask/24.3.1/elpa/
archives  popwin-20130329.435

これで .cask ディレクトリが作成され、依存ライブラリがダウンロードされます。

この状態で

$ cask exec command

することで、 .cask 以下にある依存ライブラリを PATHload-path に追加し た状態で command を実行することができます。したがって以下のコマンドでテスト を実行することができます。

$ cask exec emacs -batch -L . -l test/guide-key-test.el -f ert-run-tests-batch-and-exit
Running 1 tests (2014-02-23 12:56:40+0900)
   passed  1/1  guide-key-test/get-highlight-face

Ran 1 tests, 1 results as expected (2014-02-23 12:56:40+0900)

無事テストが成功しました。

Caskを使えばpopwinへの load-path を考える必要がないのが楽です。ただしCaskは リポジトリ直下(guide-key.elがあるディレクトリ)を load-path に追加してくれ ないようなので、 -L . で手動で追加しています。あまり美しい方法では無いですね。 参考にしたリポジトリでは、 test/test-init.el などのテスト初期化ファイルを作っ て、そこで開発ライブラリ(guide-key.el)をロードするような構成になっているもの もありました。

環境変数で環境を切り替える

環境変数 EMACS を設定することで、Caskで利用するEmacsを切り替えることができま す。上の emacs はバージョンが24でしたが、それとは別にバージョン23の emacs23 がインストールされている場合、以下のように cask を環境変数を変更し て実行します。

$ export EMACS=emacs23
$ cask install
Contacting host: marmalade-repo.org:80
Saving file /home/kai/.emacs.d/my-lisp/guide-key/.cask/23.3.1/elpa/archives/marmalade/archive-contents...
(中略)
Wrote /home/kai/.emacs.d/my-lisp/guide-key/.cask/23.3.1/elpa/popwin-20130329.435/popwin.elc
Done (Total of 2 files compiled, 1 skipped)
$ ls .cask/23.3.1/elpa
archives  ert-0  popwin-20130329.435

あとは先ほどと同様に

$ cask exec ${EMACS} -batch -L . -l test/guide-key-test.el -f ert-run-tests-batch-and-exit
Running 1 tests (2014-02-23 12:56:40+0900)
   passed  1/1  guide-key-test/get-highlight-face

Ran 1 tests, 1 results as expected (2014-02-23 12:56:40+0900)

でテストが回せます。popwinやertへの load-path を考える必要がなく、同じコマン ドなのがいいですね。

Travis CIでCI

テストが書けたのでCIできるようにTravis CIを利用します。

Makefileで自動化

make コマンド一発でテストを回すために、Makefileを作ります。

EMACS ?= emacs
CASK ?= cask

all:
    ${MAKE} clean
    ${MAKE} test
    ${MAKE} compile
    ${MAKE} test
    ${MAKE} clean

compile:
    # Fail if byte-compile outpus warnings
    ${CASK} exec ${EMACS} -batch -Q -L . -eval \
    "(progn \
    (setq byte-compile-error-on-warn t) \
    (batch-byte-compile))" guide-key.el
test:
    ${CASK} exec ${EMACS} -Q -batch -L . -l test/guide-key-test.el -f ert-run-tests-batch-and-exit
clean:
    rm -f guide-key.elc

.PHONY: all compile test clean

簡単なMakefileですが make コマンドでバイトコンパイルせずにテストと、バイトコ ンパイルしてテストを実行します。バイトコンパイルで警告が出ると失敗させているの は厳しすぎるかもしれませんが、当面これで行くことにしました。

Travisの設定

Travisのビルド設定をtravis.ymlに書きます。

language: emacs-lisp
env:
  - EMACS=emacs23
  - EMACS=emacs24
  - EMACS=emacs-snapshot
matrix:
  allow_failures:
    - env: EMACS=emacs-snapshot
before_install:
  # Install Emacs
  - sudo add-apt-repository -y ppa:cassou/emacs
  - sudo apt-get update -qq
  - sudo apt-get install -qq $EMACS
  # Install Cask
  - curl -fsSkL --max-time 10 --retry 10 --retry-delay 10
        https://raw.github.com/cask/cask/master/go | python
  - export PATH="$HOME/.cask/bin:$PATH"
  - cask
script:
  make

before_install で必要なEmacsとCaskをインストールして、テストを回します。テス ト環境は emacs23emacs24emacs-snapshot の3つとし、環境変数 EMACS を設定することで自動的に cask の動作が変わるようになります。

実際にビルドした結果が以下のようになります。

guide-keyのTravis CIでのビルド結果

emacs-snapshot がなぜか失敗しているので、やむを得ず allow_failures に入れ てます。

まとめ

Emacs Lispのテスト、依存性管理、CIする方法を紹介しました。最終的なディレクトリ 構成は以下のようになりました。

guide-key/
├── .cask/          # 依存ライブラリを格納
│   ├── 23.3.1/      # Emacsのバージョン別に保持
│   ├── 24.3.1/
│   └── ...
├── .travis.yml     # Travis CIの設定
├── Cask            # 依存ライブラリを記述
├── Makefile        # テストの自動化
├── guide-key.el
└── test/
    └── guide-key-test.el

あとはテストケースが全然不十分なので、テストケースを充実させていくだけです。 guide-keyは副作用がある関数ばかりなので、テストが書きにくそうです。できるだけ 副作用のない粗結合の構成になるようにリファクタリングしたいと思います。

さらにテストを便利にするためのライブラリとして、rejeep/ert-runner.elecukes/ecukesがあります。ert-runnerはJUnitでいうテストスイートのようなもので、 テスト名やタグによって実行するテストケースを制御するライブラリです。ecukesは cucumberのように振る舞い駆動開発するためのライブラリのようです。

これらもおいおい導入していければと思います。

参考にしたリポジトリ

Comments