Neovim luaのテストを書こう。neotestで高速に修正🔁テストのサイクルを回す
はじめに
今回はNeovim luaのテストを書く方法を紹介します。
Lua製プラグインはもちろんのこと、dotfilesで便利関数を書いている人にも試してほしいです。
Neovimで初めてluaを書いた人も多いでしょうし、「正直luarocksとかよくわからないんだよなー」という方もいらっしゃることでしょう (自己紹介) 。
Lua製プラグインはNeovimだけで動くのだから、プラグインのテストもNeovimで動いて欲しいというのが人情です。
今回は以下のプラグインを活用する方法を紹介します。
日本語圏ではvustedが人気な印象です。plenaryのtest_harnessとneotestの組み合わせも、素晴らしい開発者体験が得られるので是非試してみて欲しいです。
テストを書いてみる
事前準備
テストを書く前にplenary.nvimをインストールしておいてください。
遅延読み込みをしている場合は読み込んでおきましょう。
はじめてのテストコード
さて、早速ですがプラグインの場合はリポジトリのトップにtestフォルダを作りましょう。
dotfilesの便利関数のテストを書く場合は.config/nvim/にtestフォルダを作りましょう。
testの中にhello_spec.luaを以下の内容で作成してみましょう。
describe('Hello', function()
  it('Test', function()
    assert.is_true(true)
  end)
end)
書いたらNeovimで:PlenaryBustedDirectory testを実行してみましょう。
floating windowで以下が表示されたら成功です。
Starting...Scheduling: test/hello_spec.lua
========================================
Testing:        /Users/yuys13/.config/nvim/test/hello_spec.lua
Success ||      Hello Test
Success:        1
Failed :        0
Errors :        0
========================================
あとはluaディレクトリ内に書いたコードをrequireしてテストコードを足すだけです。
今回はテストコードそのものについては触れません。
私は以下を参考にしてテストを書いています。
(spyとstubはソース読んでしまいました)
- plenaryのREADME.mdのtest_harness
 - Testing Guide
 - luassert
 
コマンドラインでテストを動かす
Neovim内でテストが実行できることはわかりました。
しかしCIで動かすことを考えるとコマンドラインから実行したいところです。
また普段使っているNeovimではplenaryが読み込まれていますが、CI環境では用意する必要があります。
それではtestフォルダ内にminimal_init.luaを作成します。
local plenary_dir =
  vim.fs.joinpath(vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h:h'), '.dependencies/plenary.nvim')
if vim.fn.isdirectory(plenary_dir) == 0 then
  vim.system({ 'git', 'clone', '--filter=blob:none', 'https://github.com/nvim-lua/plenary.nvim', plenary_dir }):wait()
end
vim.opt.rtp:append(plenary_dir)
このスクリプトの動作は以下です。
- 
test/minimal_init.luaの位置から../../.dependencies/plenary.nvimの絶対パスを得る - ↑のパスが存在しなければplenary.nvimをcloneする
 - ↑のパスを
runtimepathに追加する 
このスクリプトを読み込めばplenaryがプラグインとして追加されるので、テスト実行コマンドが使えるようになります。
以下のようにテストを実行してみましょう。
$ nvim --headless -u test/minimal_init.lua -c "PlenaryBustedDirectory test"
Starting...Scheduling: test/hello_spec.lua
========================================
Testing:        /Users/yuys13/.config/nvim/test/hello_spec.lua
Success ||      Hello Test
Success:        1
Failed :        0
Errors :        0
========================================
これでNeovimの知識だけでテストを書いて実行できるようになりました。
neotestでテストを実行して結果確認して修正するサイクルを高速で回す
やっと本題です。
neotestをインストールする
neotestとneotest-plenaryをインストールしましょう。
参考までにlazy.nvimでのインストール例です。
(plenaryは既に入っているはずなので省略)
  {
    'nvim-neotest/neotest',
    cmd = 'Neotest',
    config = function()
      ---@diagnostic disable-next-line: missing-fields
      require('neotest').setup {
        adapters = {
          require 'neotest-plenary',
        },
      }
    end,
  },
  { 'nvim-neotest/neotest-plenary' },
  { 'nvim-neotest/nvim-nio' },
neotestでテストを一覧する
:Neotest summaryを実行しましょう。
一個しかないのでイマイチですね。
neotestでテストを一括実行する
summaryのツリー上でrを押してみましょう。
ツリーにテストの結果が反映され、テストコードのバッファにも結果が表示されました。
ファイルをwatchして変更があったらテストを自動実行する
summaryのツリーでwatchしたいファイルにカーソルを合わせてwを押します。
ファイル名横のアイコンが目のようになりました。
それでは試しに失敗するテストを追加してみましょう。
絶対失敗するテストが書けました。ここでファイルを保存します。
情報量が多いですが、ツリーではFail❌がついており、テストコードのバッファでも❌がついています。
またテストの出力も表示されていますね。
失敗したテストの原因を調べる
テストの出力を見てみましょう。
まずはツリー上で確認する方法から。
出力を確認したいテストにカーソルを合わせてoを押します。
trueが期待されていたのに、falseを渡されたことがわかります。
テストコードのバッファではテストの結果がDiagnosticsとして表示されています。
そのためLanguage ServerのDiagnosticsと同じように確認可能です。
Diagnosticsなのでfloating windowの中にも入れます。
他にも:Neotest outputや:Neotest output-panelでも確認可能です。
Neotest summaryのマッピングを確認する
便利なのはわかったけどキーマップ覚えられないと言いたいのですよね。わかります。
?を押せば表示されるので安心してください。
watchだけ覚えてれば困らない気もします。
まとめ
- plenary.nvimのtest_harnessでNeovim luaのテストが書ける
- luarocksなどに依存せずNeovimがあればテスト可能
 - コマンドライン実行もできるのでCIも実現可能
 
 - neotestとneotest-plenaryで実装→結果確認→修正のサイクルを高速で回すことが可能
 
おまけ
vustedを使った方法は以下をご覧ください。
実はplenaryのtest_haernessでテストを書いておけばvustedでも実行できます。逆は動かない場合があります。
plenaryはbustedの記法の一部をサポートしていて、vustedは本物のbustedを動かしているためです。
よってplenaryからvustedへの移行は簡単に済むので、迷ったらとりあえずplenaryでテストを書き始めると良いでしょう。
カバレッジの取得はvustedの方が容易に実現できるため、カバレッジの測定のみvustedで実行する構成もありだなと考えています。
ここまで読んでくれた方へ
dotfilesの便利関数のテストは書けそうでしょうか。
おや、もう書きましたか。さすがです。
ではそれをリポジトリ分割してプラグインとして公開してみましょう。
私にもその便利関数使わせてください。
こちらからは以上です。
Discussion