testing/synctest

#golang #testing

並行処理のテストのためにGoの標準ライブラリに入ったパッケージ。テスト関数を「バブル」と呼ばれる隔離環境で走らせ、その中ではtimeパッケージが仮想時計で動く。time.Sleep(24 * time.Hour)が実時間ゼロで終わるので、タイムアウト・リトライ・バックオフのテストからtime.Sleepによる待ち合わせを追い出せる。

実際に動かして確かめた記録はtesting/synctestを動かしてみるに分けてある。

2つのAPIがある

Goバージョン API 有効化
1.24 synctest.Run(f func()) GOEXPERIMENT=synctest が必要
1.25以降 synctest.Test(t *testing.T, f func(*testing.T)) 不要(一般提供)

リリースサイクルに沿って、Go 1.25のリリースノートいわく、「このパッケージはGo 1.24でGOEXPERIMENT=synctestの下、少し違うAPIで最初に利用可能になった。実験は一般提供に昇格した。古いAPIはGOEXPERIMENT=synctestを設定すれば今も存在するが、Go 1.26で削除される」。新規に書くならTestの方。

func TestTimeout(t *testing.T) {
    synctest.Test(t, func(t *testing.T) {
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
        defer cancel()
        <-ctx.Done() // 実時間では一瞬
    })
}

バブルの中のルール

  • 初期時刻は2000-01-01 00:00:00 UTC固定。 実時計とは無関係。
  • 時間は「バブル内の全goroutineがdurably blockedになったとき」にだけ進む。 動いているgoroutineが1つでもあれば仮想時計は止まったまま。次に発火すべきタイマーの時刻まで一気に飛ぶ。
  • rootのgoroutineが終了すると時間の進行も止まる。
  • 全goroutineがdurably blockedで、発火待ちのタイマーも無ければデッドロックとしてpanicする。
  • バブルの中からRun/Testを呼ぶとpanicする(ネスト不可)。

durably blocked の定義が肝

「durably blocked(永続的にブロックされている)」に該当するのは、ドキュメント上は次の5つだけ。

  1. バブル内で作られたチャネルへの送受信でのブロック
  2. 全てのcaseがバブル内チャネルであるselectでのブロック
  3. sync.Cond.Wait
  4. sync.WaitGroup.Wait(ただしAddがバブル内で呼ばれた場合)
  5. time.Sleep

裏を返すと、ここに無いものは仮想時計を進めない。特に引っかかるのが以下。

  • sync.Mutexのロック待ちは durably blocked ではない。 そのためバブル内でmutexのデッドロックを作ってもsynctestは検出せず、実時間でハングし続ける(実験ではgo test -timeoutに引っかかるまで止まった)。
  • バブルの外で作られたチャネルを待つのも durably blocked ではない。実時間で待つことになる。
  • ネットワークI/Oやシステムコールも同様。実際のI/Oを伴うコードは、バブルに入れる前にインターフェイス越しに差し替えておく必要がある。

synctest.Wait

「自分以外のバブル内の全goroutineがdurably blockedになるまで」待つ。「goroutineを起動した → そいつがチャネル待ちに入るまで待ってから次に進む」という同期を、time.Sleep(10 * time.Millisecond)のような当て推量なしに書ける。並行テストのflakyさの主因を潰せるのはこちらの機能。

何が嬉しいか

  • タイムアウトのテストが実時間を消費しない(10分のタイムアウト → 実測35µs)
  • 指数バックオフの全試行を一瞬で回せる(17分ぶんのリトライ → 実測88µs)
  • テスト内にtime.Sleepで「たぶんこれくらい待てば終わってるだろう」と書かずに済む
  • goroutineリークがデッドロックpanicとして自動的に見つかる

出典

作成日時: 2026-09-11 05:53 / 更新日時: 2026-09-11 05:53