NeoBundle + Vesting 使ってVim plugin テストを自動化

# はじめに
最近, Vim pluginを開発するようになって, テストをCIでぶん回したくなったので, 試行錯誤の結果メモ.

# やりたいこと

僕はあんまりテストに深い思い入れがある訳ではないのだけど, 下記を実現したい気持ちがあった.

1. コーディング中はVimから単体テストを実行したい.
1. CIでもテストを回したいため, CLIからの実行も欲しい. READMEに"build passing"のバッジ貼りたい.
1. テストを実行するための依存プラグイン管理の仕組みが欲しい.

Vim plguinのCIについて適当にググってみたところ, [vim-flavor](https://github.com/kana/vim-flavor)でテストをCLIから実行する, という方法が見つかったのだが, これは上記の1. と3. と背反するため, 今回は採用見送り.
また, vim-flavorは依存プラグインのバージョン指定がsemverに従ってないと上手く動作しない様子だったため, これも採用見送りの理由の一つ.

結局, 上記を全部充足するようなVimプラグインは特に見つからなかったので, NeoBundle + vesting + シェルスクリプトの構成を採用することにした.
NeoBundleは普段から使っているplugin manager, という理由.
vestingについては, 特に深い思いがある訳ではないが, NeoBundleと作者が一緒であり, 上記の1.に対応していたため.

# やってみた

## 基本的な考え方

先述の通り, Vimを立ち上げてからの実行はvestingで出来るので, 作り込む必要があるのはCLI部分のみ.
今回は以下のアプローチでやってみた.

1. レポジトリにneobundle.vimをclone.
1. テスト用の.vimrcを用意し, テストに必要なVimの設定情報は事前に記述しておく.
 * プラグイン本体, NeoBundleの`runtimepath`設定はこのファイルに記述
 * テスト実行時に必要となる依存pluginはこのファイル中に`:NeoBundle` コマンドで列挙し, `:NeoBundleInstall` で取得する
1. vestingを実行するためのマクロを用意する.
 * vestingはUniteで結果を出力するため, この結果をログに吐き出すようにしておく.
1. Vimをsilent-modeで起動する. `vim -u vest/.vimrc -s vest/_runner`.
1. vestの結果ログに`[Fail]`, `[Error]`が存在するかどうか, grepする. 該当無しであればテスト成功とみなす.

## コード解説
さて, 実際に[Quramy/vesting-ci](https://github.com/Quramy/vesting-ci)にサンプルのレポジトリ作って, TravisCIで動かしてみた.

レポジトリのディレクトリ構成は下記のようにしている.

```txt:ディレクトリ構成
- vesting-ci           # レポジトリのルート. 
 - autoload/
  |  calc.vim          # テスト対象コード
 + bundle/             # NeoBundleでinstallするpluginディレクトリ
 + neobundle.vim/      # NeoBundle本体
 - vest/               # vestingのテストコード用ディレクトリ
  |  .vimrc            # 2.の設定ファイル
  |  _runner           # 3.のvesting実行マクロ
  |  calc_add.vim      # vestingのテストコードその1
  |  calc_sub.vim      # vestingのテストコードその2
 + vim/
   runtest.sh          # CLI用シェルスクリプト
   .travis.yml         # TraviceCI設定ファイル
```

vestingはカレントディレクトリ配下の`vest/*.vim`をテストコードとみなして実行するため, テスト関係のファイルは極力ここに突っ込むようにした.

テスト対象のコードは下記. と言っても只の足し算と引き算.

```vim:autoload/calc.vim
function! calc#add(a, b)
  return a:a + a:b
endfunction

function! calc#sub(a, b)
  return a:a - a:b
endfunction
```

対応するvestingのテストコードは下記.

```vim:vest/calc_add.vim
scriptencoding utf-8

Context Vesting.run()
  It calc#add returns the summation of two values
    Should calc#add(1, 2) == 3
  End
End
Fin
```

`vest/calc_sub.vim`も似たような感じ. ちなみに, vestingは`:Unite vesting:.` をVimから実行すると, テストの一覧がUnite上に表示されるので, 実行したいテストファイルを選択してEnterを叩くとテストが実行される.


ここからがこのエントリの主眼. まずは上記2. の`.vimrc` から.

```vim:vest/.vimrc
if has('vim_starting')
  set nocompatible
  let s:basedir = expand('<sfile>:p:h').'/../'

  " プラグイン自体をruntimepathに登録.
  execute('set runtimepath+='.s:basedir)

  " NeoBundleをruntimepathに登録.
  execute('set runtimepath+='.s:basedir.'neobundle.vim')

  " NeoBundleで依存pluginを記載.
  " Uniteとvestingは必須.
  call neobundle#begin(expand(s:basedir.'bundle'))
  NeoBundle 'Shougo/unite.vim'
  NeoBundle 'Shougo/vesting'
  call neobundle#end()

  " 必要なpluginの一括install
  silent NeoBundleInstall
endif
```

Uniteとvestingは最低限必要となるpluginである.  もし, 他に必要なpluginがあれば, `NeoBundle`を追記していけばよい.
([僕が実際に開発しているPlugin](https://github.com/Quramy/tsuquyomi)では, vimprocを使ってるいるが, このファイルに追記している)

続いて3.のvesting実行マクロ. 

```vim:vest/_runner
:Unite vesting:.
*
:call unite#action#do('start')
:w vest/test_result.log
:quitall
vim:ft=vim
```

vestingを実行した後, `*`で全テストファイルを選択. uniteの`start` actionを実行すると, 全テストが実行されて結果がUniteのバッファに表示されるので, これをファイルに保存するようにしている.
あんまりスマートじゃない気もするが, 取り合えず動いてるのでこれでよし.
なお, このファイルの拡張子に.vimを付けてないのは, vestingがテストコードだと誤解しないようにするため.

最後にCLI用のシェルスクリプトとTravisCIの設定ファイル.
実際はディレクトリの存在チェックとか, Vim自体のビルド処理(TravisCIのVimが7.3で古いので)も書いているため, もう少し長くなるのだけど, 先述した方法の骨子を抜きだすとこんな感じだ.

```sh:runtest.sh
#/bin/sh

VIMRC_FILE="vest/.vimrc"
DRIVER_FILE="vest/_runner"
RESULT_FILE="vest/test_result.log"

# 1. NeoBundleのクローン
git clone https://github.com/Shougo/neobundle.vim

# 4. Vestingの実行
vim -u ${VIMRC_FILE} -s ${DRIVER_FILE}
cat ${RESULT_FILE}

# 5. テスト結果の判定
grep -E "\[Fail\]" ${RESULT_FILE} > /dev/null
if [ $? -eq 0 ]; then
  exit 1
fi
grep -E "\[Error\]" ${RESULT_FILE} > /dev/null
if [ $? -eq 0 ]; then
  exit 1
fi
```

```yaml:.travis.yml
script:
- sh runtest.sh
```

# 所感
最初はVim plguinのCI化は結構敷居が高そうに思っていたが, やってみると意外と簡単.

テスティングフレームワークに依存するのはマクロ部分だけなので, vesting以外のフレームワーク使ってる場合でも, 考え方とかシェルは使いまわせる予感.

# 参考

* [Test framework for Vim scripts](https://github.com/Shougo/vesting/wiki/Test-framework-for-Vim-scripts)
* [Vim プラグイン開発でも継続的インテグレーションがしたい! (Travis CI 編)](http://labs.timedia.co.jp/2013/02/vim-plugins-vs-travis-ci.html)