Neovim で neotest でのテスト環境を構築する方法

この記事では、Neovim で Golang のテストを実行するまでの手順を紹介します

必要なツールと前提条件

  • Neovim: v0.10.5
  • Go: Go の実行環境および go.mod で管理されたプロジェクトが必要です。
  • LazyVim / lazy.nvim
    • Neovim の設定管理ツールとして LazyVim を使用しています。
  • プラグイン

neotest の基本設定

私はrafi/vim-configを利用しています。 “extras” plugins として lazyvim.plugins.extras.test.core がリストの中にありました。 :LazyExtras でLazyVimのメニューを出し、testで検索、xで有効、neovimの再起動でインストールされました。

neotest 使い方

<leader> t でテスト関連のメニューが開き

  • t テストファイルを開いた状態で実行すると、そのテストファイルの内容が実行される
  • T と押すと全てのテストが実行

まずは、nvim-dap を LazyVim の仕組みを利用して設定します。 以下のサンプル設定では、Go 用のアダプタとして Delve を利用し、単一ファイル実行と動的な CLI 引数入力の2種類のデバッグ構成を用意しています。

-- ~/.config/nvim/lua/plugins/nvim-dap.lua
return {
  "mfussenegger/nvim-dap",
  event = "VeryLazy",  -- lazy.nvim の遅延読み込みイベント
  config = function()
    local dap = require("dap")

    -- Go 用のアダプタ設定(Delve を利用)
    dap.adapters.go = {
      type = "executable",
      command = "dlv",
      args = { "dap" },
    }

    -- デバッグ構成の定義
    dap.configurations.go = {
      {
        type = "go",
        name = "Launch file",
        request = "launch",
        program = "${file}",
        cwd = "${workspaceFolder}",
      },
      {
        type = "go",
        name = "Launch file (with arguments)",
        request = "launch",
        program = "${file}",
        cwd = "${workspaceFolder}",
        args = function()
          local input = vim.fn.input("Enter CLI arguments: ")
          return vim.split(input, " ", { trimempty = true })
        end,
      },
      {
        type = "go",
        name = "Launch package (with arguments)",
        request = "launch",
        program = "${workspaceFolder}",  -- パッケージ全体をビルド対象にする
        cwd = "${workspaceFolder}",
        args = function()
          local input = vim.fn.input("Enter CLI arguments: ")
          return vim.split(input, " ", { trimempty = true })
        end,
      },
      {
        type = "go",
        name = "Debug tests",
        request = "launch",
        mode = "test",
        program = "${file}",
      },
    }

    -- キーマッピングの設定例
    vim.keymap.set("n", "<leader>db", dap.toggle_breakpoint, { desc = "Toggle Breakpoint" })
    vim.keymap.set("n", "<leader>dc", dap.continue, { desc = "Start Debugging" })
    vim.keymap.set("n", "<leader>di", dap.step_into, { desc = "Step Into" })
    vim.keymap.set("n", "<leader>do", dap.step_over, { desc = "Step Over" })
    vim.keymap.set("n", "<leader>dO", dap.step_out, { desc = "Step Out" })
  end,
}

ポイント

  • program と cwd の設定
    • 単一ファイルの場合は ${file}、複数ファイルを含むパッケージの場合は ${workspaceFolder} を指定します。
  • 動的 CLI 引数入力
    • args を関数にして、デバッグ開始時にユーザーから CLI 引数を入力させるようにしています。

nvim-dap-ui の導入と設定

続いて、視覚的にデバッグ情報を表示するために nvim-dap-ui を設定します。 nvim-dap-ui は nvim-dap に依存するため、LazyVim の設定では dependencies を指定しておきます。

-- ~/.config/nvim/lua/plugins/nvim-dap-ui.lua
return {
  "rcarriga/nvim-dap-ui",
  event = "VeryLazy",
  dependencies = { "mfussenegger/nvim-dap" },
  config = function()
    local dap = require("dap")
    local dapui = require("dapui")

    dapui.setup({
      icons = { expanded = "▾", collapsed = "▸" },
      mappings = {
        expand = { "<CR>", "<2-LeftMouse>" },
        open = "o",
        remove = "d",
        edit = "e",
        repl = "r",
      },
      layouts = {
        {
          elements = { "scopes", "breakpoints", "stacks", "watches" },
          size = 40,
          position = "left",
        },
        {
          elements = { "repl", "console" },
          size = 10,
          position = "bottom",
        },
      },
      floating = {
        border = "rounded",
        mappings = {
          close = { "q", "<Esc>" },
        },
      },
      windows = { indent = 1 },
    })

    -- デバッグ開始時に自動で UI をオープンし、終了時にクローズする設定
    dap.listeners.after.event_initialized["dapui_config"] = function()
      dapui.open()
    end
    dap.listeners.before.event_terminated["dapui_config"] = function()
      dapui.close()
    end
    dap.listeners.before.event_exited["dapui_config"] = function()
      dapui.close()
    end

    -- UI のトグル用キーマッピング
    vim.keymap.set("n", "<leader>du", dapui.toggle, { desc = "Toggle DAP UI" })
  end,
}

ポイント

  • 依存関係の指定
    • dependencies に “mfussenegger/nvim-dap” を追加して、nvim-dap-ui の設定時に nvim-dap が必ずロードされるようにしています。
  • イベントリスナー
    • デバッグセッションの開始・終了イベントに合わせて、DAP UI を自動で開閉するように設定しています。

使い方

上記の設定だと nvim-dap-ui の起動は <leader>du に設定しています。 dap の基本操作は <leader>dc でデバッグを開始し、<leader>db でブレークポイントをトグルします。 私は <leader> をスペースで設定しているので space -> d -> u で nvim-dap-ui を開閉できます。 nvim-dap-uiの再生ボタンで開始すると、 コマンドラインに入力を求められるので、引数を入力してください。 ステップ実行が始まり、再生ボタンをクリックする事で次のブレークポイントに移動、 隣のボタンで(ブレークポイントではなく)次のステップを実行します、Golangの場合、ライブラリの処理に移動したりするので興味深かったです。 実際には再生ボタンでブレークポイントに移動しつつエラーを追い、ライブラリなど細かい挙動を見たい時には隣のボタンを押す、みたいな使い方をしています。 (もっと上手い使い方はありそうですが)

よくあるエラーと対策

  1. モジュールが見つからないエラー

エラー例:

module 'dap' not found:

→ この場合、nvim-dap のプラグインが正しく読み込まれていない可能性があります。 LazyVim の設定で依存関係を明示するか、lazy loading のタイミングを確認してください。

  1. Build Error: go build … directory not found
stat /path/to/project/[dap-repl-9]: directory not found

→ Delve がビルド対象として誤ったパスを参照している場合、program と cwd の設定を見直し、プロジェクトルート(${workspaceFolder})を指定する必要があります。上記のサンプル設定では、複数ファイルのパッケージ全体の実行をサポートするために cwd を設定しています。

テストファイルの取り扱い

Go の実行(go run)では、*_test.go ファイルは自動的に除外されるため、通常のデバッグ実行でテストファイルが含まれる心配はありません。 ただし、テスト実行時には専用のデバッグ構成(例:mode = “test” を利用)を用意することで、go test のデバッグを実行できます。

まとめ

この記事では、Neovim 上で Golang のデバッグ環境を構築するための設定例を紹介しました。

  • nvim-dap を利用して Delve ベースのデバッグ構成を設定
  • 動的な CLI 引数入力機能の実装
  • 複数ファイル(パッケージ)対応のための program と cwd の設定
  • nvim-dap-ui による視覚的なデバッグ情報の表示と自動開閉機能の設定

参考リンク