「Vim プラグイン開発でも継続的インテグレーションがしたい! (Travis CI 編)」
では Vim プラグイン開発でも簡単に継続的インテグレーション(CI)を導入できることを解説しました。
ところで CI はユニットテストを書くことが前提です。
上記の記事では「どうユニットテストを書くか」についてはほとんど触れていませんでした。
実際にテストを書こうとすると、
という割と面倒臭い問題があります。
しかしもう2013年です。
どうにかして簡単にユニットテストを書けないものでしょうか。
Vim プラグインのユニットテストを行うフレームワークは実装が乱立していますが、基本的にどれも
のいずれかの難点を抱えています。
という訳でこれらの難点を全てクリアできるものを実装しました(→ vim-vspec)。
これを使ってユニットテストを始めてみましょう。
t
という名前のディレクトリを作成します。.vim
にして t
の中に保存します。こと Vim プラグインのテストとなると Vim に関する細かい操作を記述する必要があります。
そして Vim を扱うには Vim が一番向いています。
なのでテストスクリプトは Vim script で記述します。
テストスクリプトの構成はおおよそ RSpec と同じです。
つまり、
という構成です。具体的には以下のようなイメージです:
describe 'math#round_to_zero'
it 'returns 0 as is'
Expect math#round_to_zero(0) == 0
end
it 'returns a floor of a positive number'
Expect math#round_to_zero(0.1) == 0
Expect math#round_to_zero(1) == 1
Expect math#round_to_zero(1.23) == 1
Expect math#round_to_zero(123.456) == 123
end
it 'returns a ceiling of a negative number'
Expect math#round_to_zero(-0.1) == 0
Expect math#round_to_zero(-1) == -1
Expect math#round_to_zero(-1.23) == -1
Expect math#round_to_zero(-123.456) == -123
end
end
結果の比較には Expect
を使います。
以下のような感じで書きます:
Expect foo#bar#baz() ==# 'qux'
==
以外にも Vim の比較演算子なら何でも使えます。Expect X not == Y
と書きます。先述の「前提」通りに設定を行っていれば rake test
でテストが実行できます。
実行例は以下のような感じです:
$ rake test
bundle exec vim-flavor test
-------- Preparing dependencies
Checking versions...
Use kana/vim-textobj-user ... 0.3.12
Use kana/vim-vspec ... 1.1.0
Deploying plugins...
kana/vim-textobj-user 0.3.12 ... skipped (already deployed)
kana/vim-vspec 1.1.0 ... skipped (already deployed)
Completed.
-------- Testing a Vim plugin
Files=0, Tests=0, 0 wallclock secs ( 0.00 usr + 0.11 sys = 0.11 CPU)
Result: NOTESTS
t/c.vim .... ok
t/vim.vim .. ok
All tests successful.
Files=2, Tests=12, 2 wallclock secs ( 0.08 usr 0.72 sys + 0.21 cusr 0.97 csys = 1.98 CPU)
Result: PASS
もしテストが失敗した場合は以下のような表示になります:
$ rake test
bundle exec vim-flavor test
-------- Preparing dependencies
Checking versions...
Use kana/vim-textobj-user ... 0.3.12
Use kana/vim-vspec ... 1.1.0
Deploying plugins...
kana/vim-textobj-user 0.3.12 ... skipped (already deployed)
kana/vim-vspec 1.1.0 ... skipped (already deployed)
Completed.
-------- Testing a Vim plugin
Files=0, Tests=0, 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
Result: NOTESTS
t/c.vim .... 1/?
not ok 1 - <Plug>(textobj-function-a) selects the next function if there is no function under the cursor
# Expected line("'<") == 3
# Actual value: 2
# Expected value: 3
t/c.vim .... Failed 1/6 subtests
t/vim.vim .. ok
Test Summary Report
-------------------
t/c.vim (Wstat: 0 Tests: 6 Failed: 1)
Failed test: 1
Files=2, Tests=12, 1 wallclock secs ( 0.03 usr 0.02 sys + 0.11 cusr 0.06 csys = 0.22 CPU)
Result: FAIL
rake aborted!
Command failed with status (1): [bundle exec vim-flavor test...]
Tasks: TOP => test
(See full trace by running task with --trace)
テストによっては定型の初期化や後始末が必要になることがあるでしょう。
その場合は before
/after
を使って簡略化できます。
例えば独自のオペレーターを定義した場合、
動作確認のためには適当なテキストで埋められたバッファが利用ケース毎に必要です。
これは以下のような感じで記述できます:
describe '...'
before
new
put =[
\ 'foo',
\ 'bar',
\ 'baz',
\ '...',
\ ]
end
after
close!
end
it '...'
...
end
it '...'
...
end
end
実際にテストを書く場合、
「一先ずテストすべき項目を列挙しておき、テストの内容の記述は後回しにする」
ということはよくやります。
このような「未実装のテスト」は TODO
を使って表現します:
it '...'
TODO
end
TODO
のテストは常に失敗扱いになり、実行結果でも良い感じに表示されます:
$ rake test
...
-------- Testing a Vim plugin
Files=0, Tests=0, 0 wallclock secs ( 0.00 usr + 0.04 sys = 0.04 CPU)
Result: NOTESTS
t/c.vim .... 1/?
not ok 1 - # TODO <Plug>(textobj-function-a) selects the next function if there is no function under the cursor
t/c.vim .... ok
t/vim.vim .. ok
All tests successful.
Files=2, Tests=12, 1 wallclock secs ( 0.05 usr 0.46 sys + 0.13 cusr 0.40 csys = 1.04 CPU)
Result: PASS
ものによっては特定の環境でしか意味を成さないテストもあります。
そういうテストは SKIP
を使って表現します:
it '...'
if executable('git') < 1
SKIP 'Git is not available.'
endif
...
end
SKIP
されたテストは常に成功扱いになり、実行結果でも良い感じに表示されます:
$ rake test
...
-------- Testing a Vim plugin
Files=0, Tests=0, 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
Result: NOTESTS
t/c.vim .... 1/?
ok 1 - # SKIP <Plug>(textobj-function-a) selects the next function if there is no function under the cursor - 'Git is not available.'
t/c.vim .... ok
t/vim.vim .. ok
All tests successful.
Files=2, Tests=12, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.08 cusr 0.02 csys = 0.14 CPU)
Result: PASS
やろうと思えば
Expect
で使う比較演算子のカスタマイズということもできます。
これだけ道具が揃っていれば割と快適にテストが書けるはずです。やりましたね。