Vim scriptのテストを行うvimtestプラグインを書いた

Vim scriptのテスティングフレームワークはいくつかあります。
以前調べたこともあります。
UTを使ってVim scriptのテストを書く - ぼっち勉強会

既存プラグインでも充分に機能を備えているのですが、以下の点で不満がありました。

  • Vimから気軽に実行できない
    • Vim scriptで関数をテストしたいだけなのに端末からシェルやバッチ叩いて実行とかメンドイ
    • 中にはRubyとか外部ツールに依存しているものもあってメンドイ
  • 出力結果が物足りない
  • 更新されてなかったりする

ということで、せっかくだから勉強がてら自分で作ってみました。
kannokanno/vimtest · GitHub

使い方

※追記
以下に書かれている使い方は、一部古い情報があります。
最新の情報についてはREADMEもしくはヘルプ、もしくはsample.vimを参考にしてください。
※追記ここまで

sample/sample.vimにサンプルがありますが、簡単にご紹介します。

最初にテストケースのインスタンスを作成します。
引数にはテストケース名を渡すことができ、未指定の場合は"Test"となります。

let testcase = vimtest#new()
let testcase = vimtest#new('さんぷる')

あとはこのtestcaseにテストを追加していきます。
ここで日本語が使えないのが痛いなーと感じています。

function! testcase.hogehoge_test()
  let x = 1
  let y = 2
  call self.assert.equals(3, x + y)
endfunction

最後に、実行するためにvimtest#run()を書いてください。

全体ではこのようになります。

let testcase = vimtest#new('さんぷる')
function! testcase.hogehoge_test()
  let x = 1
  let y = 2
  call self.assert.equals(3, x + y)
endfunction
call vimtest#run()

あとはこのファイルをsourceすればOKです。
実行用のコマンドは特にありません。
ぼくはquickrunを使っているので、これがあればいいやと思って今のところなしです。
例えば下記のようなautocmdを定義しておけば保存時に勝手にテストしてくれて便利です。

autocmd BufWritePost,FileWritePost *_test.vim QuickRun

実行すると次のように出力されます。

.
Test cases run: 1, Passes: 1, Failures: 0

失敗メッセージ

以下のような失敗だらけのテストケースがあるとして

let testcase = vimtest#new('failed test1')
function! testcase.failed()
  call self.assert.equals(2, 1)
endfunction
function! testcase.error()
  hogeee
endfunction

let testcase = vimtest#new('failed test2')
function! testcase.failed()
  call self.assert.equals(2, [])
endfunction
call vimtest#run()

これを実行した際の出力はこうなります。

EFE

# failed test1
  1) failed
      Failed asserting expected:<2> but was:<1>

  2) error
      Excpetion:Vim:E492: エディタのコマンドではありません:   hogeee in function vimtest#run..1283..1286, 行 1


# failed test2
  1) failed
      Excpetion:Vim(if):E691: リスト型はリスト型としか比較できません in function vimtest#run..1298..1300..1287, 行 9

FAILURES!
Test cases run: 3, Passes: 0, Failures: 3

ちょっと微妙なのが、エラーの発生箇所がvimtest#run内部になってしまいます。
これをちゃんと呼び出し元の情報を表示したいんですが、やり方が分かりませんでした。

setupとか

あります。
以下の名前で定義してもらえれば動きます。

  • startup テストケース開始前に一度だけ呼ばれる
  • setup テストメソッド開始前に都度呼ばれる
  • teardown テストメソッド終了後に都度呼ばれる
  • shutdown テストケース終了後に一度だけ呼ばれる
let testcase = vimtest#new('prepare flow')
function! testcase.startup()
  echo 'startup'
endfunction
function! testcase.setup()
  echo '  setup'
endfunction
function! testcase.test_one()
  echo '   test one'
endfunction
function! testcase.test_two()
  echo '   test two'
endfunction
function! testcase.test_three()
  echo '   test three'
endfunction
function! testcase.teardown()
  echo '  teardown'
endfunction
function! testcase.shutdown()
  echo 'shutdown'
  echo ''
endfunction

call vimtest#run()
startup
  setup
   test three
  teardown
  setup
   test two
  teardown
  setup
   test one
  teardown
shutdown

Test cases run: 0, Passes: 0, Failures: 0

ユーザー定義変数or関数

self.customに追加できます。
定義した値はもちろんテストメソッドの中から参照できます。
vimtest#newによって返されるインスタンスは異なるので、testcase単位で共通的な情報を持つことができます。

let testcase = vimtest#new('custom')
function! testcase.setup()
  " self.customはdictなので、変数を持たせたい場合は辞書で再代入する
  let self.custom = {'expected': 0}
  " dictなのでfunctionを追加することも可能
  function! self.custom.dataProvider()
    return [2, 4, 6]
  endfunction
endfunction
function! testcase.test_one()
  for d in self.custom.dataProvider()
    call self.assert.equals(self.custom.expected, d % 2)
  endfor
endfunction
function! testcase.test_two()
  for d in self.custom.dataProvider()
    call self.assert.false(d % 2)
  endfor
endfunction

let testcase = vimtest#new('another custom')
function! testcase.test_another()
  " 別オブジェクトなので未定義のためエラー
  for d in self.custom.dataProvider()
    call self.assert.false(d % 2)
  endfor
endfunction

call vimtest#run()
......E

# another custom
  1) test_another
      Excpetion:Vim(for):E716: 辞書型にキーが存在しません: dataProvider() in function vimtest#run..1576..1578, 行 2

FAILURES!
Test cases run: 7, Passes: 6, Failures: 1

assert

まだ少ないです。執筆時点では以下の4つしかありません。

  • self.assert.equals(expected, actual)
    • expected ==# actual ならOK
  • self.assert.notEquals(x, y)
    • x ==# y じゃなければOK
  • self.assert.true(actual)
    • actualが1ならOK
  • self.assert.false(actual)
    • actualが0ならOK


既存プラグインと比べると機能的に物足りない部分は色々あります。
バッファのテストとかできませんし。複数のテストファイルをまとめて実行も今はできません。
ただ個人的な好みである「Vimから気軽に実行したい」は一応出来たかなと。
あとは実際に自分で使いながら拡張していこうかなと思っています。