testing: allow examples with any signature
要約
概要
testing パッケージのサンプル関数(Example関数)に任意のシグネチャを許可するproposalです。現在は引数なし・戻り値なしの関数しかExample関数として認識されませんが、この変更により *testing.T を引数に取ったり、error を返すような関数もドキュメント上のExampleとして表示できるようになります。
ステータス変更
(新規) → active
2026年6月10日のProposal Review Meeting(@aclements, @adonovan, @cherrymui, @griesemer, @ianlancetaylor, @neild, @rolandshoemaker 参加)にて、本proposalが議事録に追加され「active」(週次レビュー対象)に移行しました。同会議では、関連する #64993(*testing.T を引数に取るExampleを許可する案)と #21111(Exampleが error を返せるようにする案)がともに「declined」(却下)された一方、本proposalはそれらを包括的に解決するより簡潔なアプローチとして議論が継続されることになりました。
技術的背景
現状の問題点
現在のGoでは、Example関数は引数なし・戻り値なしでなければなりません。これはいくつかの実用的なケースで問題になります。
// 現在:エラーを無視するか、log.Fatalに頼るしかない
func ExampleOpen() {
f, err := os.Open("testfile")
if err != nil {
log.Fatal(err) // 実際のコードでは return err と書くはず
}
defer f.Close()
examplepackage.F(f)
}
// テストフレームワークのExample(現在は書けない)
func ExampleMyFramework_Run(t *testing.T) { // コンパイルエラー
myframework.Run(t, func() { ... })
}
特にテストユーティリティライブラリやエラーを返す関数のExampleを書く場合、実際のコードとかけ離れた不自然な書き方を強いられていました。
提案された解決策
go doc および go/doc パッケージが Example または ExampleXxx(Xxx が小文字で始まらない)という名前の関数を、シグネチャにかかわらずExampleとして認識・表示するようにします。
- 引数や戻り値がある場合、
go docは関数全体をそのまま(非実行形式の)Exampleとして表示する testingパッケージは引数または戻り値を持つExampleを自動実行しない// Output:コメントが付いた引数あり/戻り値ありのExampleに対してgo testはエラーを報告する
// 提案後:自然なGoコードとしてExampleを書ける
func Example_returning_an_error() error {
f, err := os.Open("testfile")
if err != nil {
return err // 実際のコードそのまま
}
defer f.Close()
examplepackage.F(f)
return nil
}
func ExampleFunc_taking_a_t(t *testing.T) {
if err := examplepackage.Func(t); err != nil {
t.Fatal(err)
}
}
func Example_in_and_out(a, b int) string {
return examplepackage.AddReturningString(a, b)
}
これらはテストパッケージによって自動実行はされませんが、対応するTest関数を書くことでテスト可能です。
これによって何ができるようになるか
1. エラーを返す自然なExampleコードの記述
エラー処理を含む実際のコードに近い形でExampleを書けるようになり、ドキュメントとして実践的な価値が高まります。
2. テストフレームワークのExampleサポート
*testing.T を引数に取るテストユーティリティライブラリ(例: vmtest, synctest など)のExampleをgo docに表示できます。
3. 引数ありの「使い方のデモ」Example
HTTPトランスポートの設定など、コードの使用方法を示す(実行は不要な)Exampleを書けます。
コード例
// Before: エラーハンドリングが不自然
func ExampleOpen() {
f, _ := os.Open("testfile") // エラーを無視
defer f.Close()
pkg.Process(f)
}
// After: 実際のコードと同じ書き方
func ExampleOpen() error {
f, err := os.Open("testfile")
if err != nil {
return err
}
defer f.Close()
pkg.Process(f)
return nil
}
// テストしたい場合は別途ラッパーを書く
func TestExampleOpen(t *testing.T) {
if err := ExampleOpen(); err != nil {
t.Fatal(err)
}
}
議論のハイライト
- 関連する二つのproposalが同日に却下: #64993(
*testing.Tを引数に取るExampleのみ許可)と #21111(errorを返すExampleのみ許可)は「合意に変更なし」として同日に却下された。本proposalはこれらを統合した汎用的なアプローチとして評価されている。 - シンプルさが評価された: @aclements は「一般的にExampleを書くのに役立ち、シンプルで良い」とレビューコメントで述べ、activeステータスへの移行を主導した。
// Output:コメントとの互換性問題: シグネチャが変わるとテストハーネスが自動実行できなくなるため、標準出力の検証に使われる// Output:コメント機構が使えなくなる。testing.Benchmarkに類似したtesting.Test関数の追加が代替案として @neild から提案されている。- 失敗を期待するExampleのテスト:
t.Fatalを呼ぶExampleをどうテストするかについて議論があったが、サブプロセスでテストを実行する方法(testing/synctestのrunTestパターン)が紹介され、エルゴノミクスは低いが実現可能との見解が示された。 - 「使い方デモ」と「効果デモ」の二種類: @neild は現在のExampleが「コードの効果を示すもの(実行可能)」と「使い方を示すもの(実行不要)」の二種類に分類できると指摘し、本proposalは後者のサポートに特に有用だと述べた。
関連リンク
- Proposal Issue github.com/golang/go
- Review Comment proposal review meeting
- Proposal Issue
- Review Minutes
- 関連Issue: testing: allow examples to return an error #21111
- 関連Issue: testing: allow examples the use of testing.T/B/F #64993
- 関連Issue: testing/synctest: add Subtest function #77320
- 関連Issue: proposal: go/doc,testing: add playable examples using a *testing.T context #74009