testing/synctest: add Subtest function
要約
概要
testing/synctest パッケージに Subtest 関数を追加するproposalです。t.Run と synctest.Test を組み合わせる煩雑な記述を1回の呼び出しで置き換え、テーブル駆動テストにおけるインデントの深さを軽減することを目的としています。
ステータス変更
likely_accept → accepted
2026年6月3日のProposal Review Meetingで「likely accept」となり、1週間の最終意見募集期間を経て、2026年6月10日のMeetingで反対意見がなかったため正式に承認されました。@aclements が議長として承認を宣言し、実装トラッキングへ移行しています。
技術的背景
現状の問題点
testing/synctest.Test はgoroutineのスケジューリングを制御する「バブル」環境を提供しますが、この関数を呼び出すと必然的にコールバック関数が1段深くなります。テーブル駆動テストで t.Run と synctest.Test を組み合わせると、テストロジックが2段分のインデントに埋もれてしまいます。
// Before: t.Run と synctest.Test を両方書く必要がある
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
// 実際のテストコード(2段深い)
})
})
}
提案者の @neild は「synctest を使うほぼすべてのパッケージでこのヘルパーを自作している」と述べており、同様のパターンが広く繰り返されていることが問題の本質です。
提案された解決策
t.Run と synctest.Test を組み合わせた Subtest 関数を testing/synctest パッケージに追加します。
func Subtest(t *testing.T, name string, f func(*testing.T)) {
t.Helper()
t.Run(name, func(t *testing.T) {
t.Helper()
Test(t, f)
})
}
実装CL(go.dev/cl/787940)も既に提出されています。
これによって何ができるようになるか
テーブル駆動テストで synctest バブルを利用する際の記述が簡潔になり、テストコードの可読性が向上します。
コード例
// Before: 従来の書き方(二重のコールバックによるインデント増加)
for _, test := range []struct {
name string
// ...
}{
{name: "test 1"},
} {
t.Run(test.name, func(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
// テストロジックがここに来る
})
})
}
// After: synctest.Subtest を使った書き方(インデント1段分削減)
for _, test := range []struct {
name string
// ...
}{
{name: "test 1"},
} {
synctest.Subtest(t, test.name, func(t *testing.T) {
// テストロジックがここに来る
})
}
ユースケースとしては以下が挙げられます。
- 並行処理のテストをテーブル駆動で体系的に書く場合
- タイムアウトやコンテキストキャンセルの挙動を複数ケースで検証する場合
- チャネルやgoroutineのライフサイクルをサブテストごとに独立したバブルで検証する場合
議論のハイライト
synctest.Runという名前案は却下:@nightlyoneがsynctest.Runと命名する案を提案したが、@neildが「バブルを*testing.Tなしで起動する関数を将来追加する際にこの名前を使う予定がある」と説明し、Subtestの名前が維持された。- 「trivial だが有用」という評価: @neild 自身が「信じられないほど些細な変更だが、信じられないほど些細であるがゆえに価値がある」と表現し、毎回自作ヘルパーを書く手間を標準化することの意義を強調した。
- @griesemer の留保: 「あまり議論の余地はないが、本当に必要かは不明」とコメントしており、必要性について若干の疑問も示されたが、最終的に反対意見とはならなかった。
- 提案の対称性を重視: 既存の
synctest.Testとtesting.T.Runを組み合わせるという自然な発想であり、APIの対称性が受け入れられた一因と考えられます。 - 実装が先行: 「likely accept」後すぐにCL(go.dev/cl/787940)が提出されており、実装の容易さがスムーズな承認につながった可能性があります。