ys memos
Blog

GodocのExampleをTestから転記するツールを作った


golang

2025/06/02


Goのドキュメントは、Godocを使って記述することで、OSSとして公開した時に pkg.go.devに自動的に公開される。

Godocでは、func ExampleFoor()のようなコードを書くことで、ExampleをTestableに記述することができるが、これは、ドキュメントに含める範囲が広くなってしまったり、アサーションがstdoutであることなど、なにかと制約が多かった。

そこで、Testのの中にアノテーションコメントを入れ、そこから関数定義のGoDocCommentsに転記するツールを go generateで実行することで、Testの一部をExampleとして利用できるようにした。


generatorは、github.com/ysuzuki19/robustruct/tree/main/cmd/gen/testdocgenに配置した。

ジェネレータのREADMEは、こちらに配置。

導入のサンプルは、こちら


Goのバージョンに合わせてツールを入れる。

tools.goを使うケースでは、以下のように記載する。

tools.go
//go:build tools
// +build tools
package tools
import (
  _ "github.com/ysuzuki19/robustruct/cmd/gen/testdocgen"
)
$ go mod tidy

コード内のどこかに(だいたい最上部か最下部?)に以下のコメントを追加する。

//go:generate go run github.com/ysuzuki19/robustruct/cmd/gen/testdocgen -file=$GOFILE

こんな感じで書く。

lib.go
//go:generate go run github.com/ysuzuki19/robustruct/cmd/gen/testdocgen -file=$GOFILE
package lib

type User struct {
	Name string
	Age  int
}

func NewUser(name string, age int) *User {
	return &User{
		Name: name,
		Age:  age,
	}
}

func (u User) GetName() string {
	return u.Name
}

上の設定をしたコードに対して、.go_test.goとしたテストファイルのみジェネレータの検出対象となる。

以下がtestdocgenのための埋め込み用のコメント例。

lib_test.go
package lib_test

import (
	"testing"

	"github.com/stretchr/testify/require"

	"example/for_testdocgen/lib"
)

func TestNewUser(t *testing.T) {
	require := require.New(t)

	// testdoc begin NewUser
	u := lib.NewUser("Alice", 30)
	require.Equal(lib.User{
		Name: "Alice",
		Age:  30,
	}, *u)
	// testdoc end
}

func TestGetName(t *testing.T) {
	require := require.New(t)

	// testdoc begin User.GetName
	u := lib.NewUser("Alice", 30)
	require.Equal("Alice", u.GetName())
	// testdoc end
}

// testdocから始まるコメントがツールの設定行となり、begin/endで囲まれた部分が、Exampleとして転記される。

転記先は、beginのあとに続けて、

  • FuncName
  • StructName.MethodName

のどちらかの形式を取る。

転記先が見つからない場合は testdocgenはエラー終了する。


$ go generate ./...

上のサンプルを実行すると、 lib.goは以下のようになる。

lib.go
//go:generate go run github.com/ysuzuki19/robustruct/cmd/gen/testdocgen -file=$GOFILE
package lib

type User struct {
	Name string
	Age  int
}

// Example:
//
//	u := lib.NewUser("Alice", 30)
//	require.Equal(lib.User{
//		Name: "Alice",
//		Age:  30,
//	}, *u)
func NewUser(name string, age int) *User {
	return &User{
		Name: name,
		Age:  age,
	}
}

// Example:
//
//	u := lib.NewUser("Alice", 30)
//	require.Equal("Alice", u.GetName())
func (u User) GetName() string {
	return u.Name
}

// Example:が書かれていないときは、既存のコメントの下に追加され、// Example:以降のコメントは、次の実行以降は自動で上書きされる。(手書きがあったとしてもそれは除去される)コメントがそもそもない場合は、// Example:から追加される。


このツールを使うことで、Exampleの更新漏れ、特に、コンパイルエラーや実装との乖離のリスクを減らせるようになった(かもしれない)。


関連タグを探す