🐍

Cobraのサブコマンドに対するテストを書きたい

に公開

GoでCLIアプリケーションを簡単に作れてしまうCobra。
サブコマンドに対するユニットテストを書こうしたら苦戦したので、解決方法を紹介します。

環境

  • Go: go 1.23.4
  • Cobra: github.com/spf13/cobra v1.8.1

[準備] サブコマンドの追加

Cobraのインストール方法やGetting startedについては公式ドキュメントをご覧ください。

それでは、サブコマンドを追加しましょう。
ここでは例として hoge というサブコマンドを追加します。

cmd/hoge.go
package cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

func init() {
	rootCmd.AddCommand(hogeCmd)
}

var hogeCmd = &cobra.Command{
	Use:   "hoge",
	Short: "Simple command to print 'Hello, Hoge!'",
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("Hello, Hoge!")
	},
}

それに対するテストコードを書いてみました。

cmd/hoge_test.go
// ❌ダメだった例
package cmd

import (
	"bytes"
	"testing"
)

func TestHogeCmd(t *testing.T) {
	buf := new(bytes.Buffer)
	hogeCmd.SetOut(buf)
	hogeCmd.Execute()

	expected := "Hello, Hoge!\n"
	output := buf.String()
	if output != expected {
		t.Errorf("expected %q, but got %q", expected, output)
	}
}

テストを実行してみます。

go test ./cmd/...
--- FAIL: TestHogeCmd (0.00s)
    hoge_test.go:17: expected "Hello, Hoge!\n", but got ""
FAIL
FAIL    awesome-tool/cmd 0.464s
FAIL

テストコードは一見良さそうなのに、なぜかoutputが取得できません。

原因

原因は2箇所ありました。

  • fmt.Println() で出力している。
  • init()rootCmd.AddCommand(hogeCmd) している。

解決方法

まず、fmt.Println()cmd.Println() に置き換えます。それによって、テストコードでSetOut() で指定した出力先に書き込まれます。普通にコマンドを実行したときは標準出力になります。

cmd/hoge.go
	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println("Hello, Hoge!")
+		cmd.Println("Hello, Hoge!")
	},

次に、AddCommand() の解決方法ですが2通りあります。

方法1: rootCmdから切り離す

init()AddCommand しているので、逆に RemoveCommand してあげます。

cmd/hoge_test.go
func TestHogeCmd(t *testing.T) {
+	rootCmd.RemoveCommand(hogeCmd)
	buf := new(bytes.Buffer)
	hogeCmd.SetOut(buf)
	hogeCmd.Execute()
}

方法2: rootCmdから実行する

あるいは、いっそのことルートコマンドから実行してあげてもOKです。その場合サブコマンドを引数として追加してあげる必要があります。

cmd/hoge_test.go
func TestHogeCmd(t *testing.T) {
	buf := new(bytes.Buffer)
-	hogeCmd.SetOut(buf)
-	hogeCmd.Execute()
+	rootCmd.SetOut(buf)
+	rootCmd.SetArgs([]string{"hoge"}) // サブコマンドを引数として追加する
+	rootCmd.Execute()
    ...
}

修正して再実行

テストが通りました 🎉

go test ./cmd/...
ok      awesome-tool/cmd 0.409s

まとめ

Cobraのサブコマンドに対するテストコードの書き方を2通り紹介しました。
これでTDD的にCLIツールを開発できますね ✌️

Discussion