🚀

NeovimでGoテストを爆速生成!gogentestプラグインで開発効率UP

に公開

はじめに

Goのテストコードを書くとき、こんな悩みはありませんか?

  • テーブルドリブンテストのボイラープレートを毎回手書きするのが面倒
  • 関数のシグネチャから引数の型を正確にコピーするのが手間
  • assert.ErrorAssertionFuncの使い方を毎回調べてしまう

そんな悩みを解決するNeovimプラグイン「gogentest」を開発しました。LSP(gopls)を活用して、カーソル位置の関数から型情報を含む完全なテストテンプレートを自動生成します。

gogentestとは

gogentestは、Goの関数からテストテンプレートを自動生成するNeovimプラグインです。

主な特徴

  • LSP(gopls)を使用して正確な型情報を抽出
  • Goland互換のテーブルドリブンテストを生成
  • goplsが使えない場合はTreesitterにフォールバック
  • ワンコマンドでテストファイルの作成から実装まで

生成されるテストの例

例えば、以下のような関数があったとします:

func ProcessData(ctx context.Context, id int, data string) (string, error) {
    if id <= 0 {
        return "", errors.New("invalid id")
    }
    if data == "" {
        return "", errors.New("empty data")
    }
    return fmt.Sprintf("processed: %s (id: %d)", data, id), nil
}

gogentestを実行すると、以下のようなテストが自動生成されます:

package mypackage_test

import (
    "context"
    "fmt"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestProcessData(t *testing.T) {
    type args struct {
        ctx  context.Context
        id   int
        data string
    }
    tests := []struct {
        name    string
        args    args
        want    string
        wantErr assert.ErrorAssertionFunc
    }{
        // TODO add cases
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ProcessData(tt.args.ctx, tt.args.id, tt.args.data)
            if !tt.wantErr(t, err, fmt.Sprintf("ProcessData(%v, %v, %v)", tt.args.ctx, tt.args.id, tt.args.data)) {
                return
            }
            assert.Equalf(t, tt.want, got, "ProcessData(%v, %v, %v)", tt.args.ctx, tt.args.id, tt.args.data)
        })
    }
}

型情報が正確に反映され、エラーハンドリングも適切に設定されているのがわかります。

導入方法

前提条件

  • Neovim 0.8以上
  • gopls(通常はGo開発環境と一緒にインストールされる)
  • オプション:nvim-treesitter(フォールバック用)

インストール

lazy.nvimを使用する場合

return {
  {
    "YuminosukeSato/gogentest",
    ft = "go",
    dependencies = { "neovim/nvim-lspconfig" },
    keys = {
      { "<leader>tG", function() require("gogentest").generate() end, desc = "Generate Go Test" },
    },
  },
}

LazyVimを使用する場合

~/.config/nvim/lua/plugins/gogentest.luaを作成:

return {
  {
    "YuminosukeSato/gogentest",
    ft = "go",
    dependencies = { "neovim/nvim-lspconfig" },
    keys = {
      { "<leader>tG", function() require("gogentest").generate() end, desc = "Generate Go Test" },
    },
  },
  
  -- goplsの設定も含める
  {
    "neovim/nvim-lspconfig",
    opts = {
      servers = {
        gopls = {
          settings = {
            gopls = {
              analyses = {
                unusedparams = true,
              },
              staticcheck = true,
            },
          },
        },
      },
    },
  },
}

基本的な使い方(ハンズオン)

では、実際にgogentestを使ってみましょう。

Step 1: サンプル関数を作成

calculator.goという名前で以下のファイルを作る:

package calculator

import (
    "errors"
)

// Add は2つの整数を加算する
func Add(a, b int) int {
    return a + b
}

// Divide は除算を行い、ゼロ除算の場合はエラーを返す
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

Step 2: テストを生成

  1. Neovimでcalculator.goを開く
  2. Add関数の上にカーソルを置く
  3. <leader>tG(デフォルトのキーバインド)を押すか、:GogentestGenerateを実行する

すると、calculator_test.goが自動的に作成され、以下のようなテストが生成される:

package calculator_test

import (
    "fmt"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
    type args struct {
        a int
        b int
    }
    tests := []struct {
        name string
        args args
        want int
    }{
        // TODO add cases
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.args.a, tt.args.b)
            assert.Equalf(t, tt.want, got, "Add(%v, %v)", tt.args.a, tt.args.b)
        })
    }
}

Step 3: テストケースを追加

生成されたテンプレートに、実際のテストケースを追加する:

tests := []struct {
    name string
    args args
    want int
}{
    {
        name: "正の数の加算",
        args: args{a: 2, b: 3},
        want: 5,
    },
    {
        name: "負の数を含む加算",
        args: args{a: -1, b: 1},
        want: 0,
    },
    {
        name: "ゼロの加算",
        args: args{a: 0, b: 0},
        want: 0,
    },
}

発展的な使い方

エラーを返す関数のテスト

Divide関数のようにエラーを返す関数の場合、gogentestはassert.ErrorAssertionFuncを使用したテンプレートを生成する:

func TestDivide(t *testing.T) {
    type args struct {
        a float64
        b float64
    }
    tests := []struct {
        name    string
        args    args
        want    float64
        wantErr assert.ErrorAssertionFunc
    }{
        {
            name: "正常な除算",
            args: args{a: 10, b: 2},
            want: 5,
            wantErr: assert.NoError,
        },
        {
            name: "ゼロ除算",
            args: args{a: 10, b: 0},
            want: 0,
            wantErr: assert.Error,
        },
    }
    // ... 
}

カスタムキーバインド

プロジェクトに応じて、キーバインドをカスタマイズできる:

vim.api.nvim_create_autocmd("FileType", {
  pattern = "go",
  callback = function()
    vim.keymap.set("n", "<leader>gt", function()
      require("gogentest").generate()
    end, { buffer = true, desc = "Generate Go test" })
  end,
})

類似ツールとの比較

gotestsとの違い

gotestsは有名なGoテスト生成ツールだが、gogentestには以下の利点がある:

機能 gogentest gotests
Neovim統合 ネイティブ 外部コマンド
LSP活用
リアルタイム型情報
Treesitterフォールバック
testify/assert対応 デフォルト オプション

gogentestは、Neovimのエコシステムに統合されており、編集中のコンテキストを活用してより正確なテストを生成できる。

内部実装の仕組み

gogentestは以下のような流れで動作する:

  1. LSPクエリ: カーソル位置でtextDocument/signatureHelpを送信
  2. シグネチャ解析: 関数名、引数、戻り値を抽出
  3. テンプレート生成: 型情報をもとに構造体とアサーションを生成
  4. フォールバック: LSPが使えない場合はTreesitterで関数名のみ取得

この仕組みにより、常に最新の型情報を反映したテストを生成できる。

トラブルシューティング

"gopls unavailable"エラー

goplsがインストールされていることを確認:

go install golang.org/x/tools/gopls@latest

その後、Neovimを再起動するか:LspRestartを実行。

型情報が取得できない

以下を確認:

  • Goファイルに構文エラーがないか
  • go mod initでモジュールが初期化されているか
  • goplsが正しく設定されているか(:LspInfoで確認)

まとめ

gogentestを使うことで、Goのテストコード作成が効率化される。特に:

  • ボイラープレートの手書きが不要
  • 型情報の正確な反映
  • エラーハンドリングが適切に実装される
  • Neovimとのシームレスな統合

ぜひ、日々のGo開発に取り入れて、テスト作成の時間を削減し、より価値のあるコード実装に時間を使ってください。

リンク

Happy Testing! 🎉

Discussion