Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
deep dive into testing/synctest
Search
Daiki Kubo
September 26, 2025
4
1.3k
deep dive into testing/synctest
Go Conference 2025登壇用のスライドです。
もし、間違いなどあれば私に連絡していただけると幸いです。
Daiki Kubo
September 26, 2025
Tweet
Share
Featured
See All Featured
RailsConf 2023
tenderlove
30
1.3k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
658
61k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
140
34k
Context Engineering - Making Every Token Count
addyosmani
8
340
Building a Scalable Design System with Sketch
lauravandoore
463
33k
Build The Right Thing And Hit Your Dates
maggiecrowley
38
2.9k
Building Adaptive Systems
keathley
44
2.8k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
Principles of Awesome APIs and How to Build Them.
keavy
127
17k
Code Reviewing Like a Champion
maltzj
526
40k
Into the Great Unknown - MozCon
thekraken
40
2.1k
Transcript
deep dive into testing/synctest 久保大貴
Daiki Kubo CyberAgent / Tapple プロフィールを見る 久保大貴 クボ ダイキ
#イラスト バックエンドエンジニア 職種 紹介 趣味 CA.go や go conference JP 等の運営メンバー ゲーム・AI分野において JSAI や IEEE Conference On Games 等での 研究論文も出している 登壇系は苦手な分野で今年は苦手な登壇を克服するを目標にしてい ました。その中で今年は Go Conference に登壇する目標をおいていた ので、無事叶って嬉しいです!頑張ります! #ゲーム 24年度入社 入社年次
1.同期 vs 非同期 これらのテストの課題とは 2.testing/synctestの紹介 3.Test(t *testing.T, f func t(*testing.T))
4.Wait() 5.Durably Blockedとは
注意事項 - 説明用にコードを簡略化しているところがあります。 - スライドにコードを載せるのに限界があり・・ - スライドは発表終了後に slideshare で公開します。 -
後の席の方などで見にくかった場合は slideshare などで見ていただけると嬉 しいです!
同期 vs 非同期 これらのテストの課題とは
同期テストの典型例 - 同期的なプログラムの結果は予測可能 - 呼ぶ → 処理 → 返る
同期テストの典型例 - 同期的なプログラムの結果は予測可能 - 呼ぶ → 処理 → 返る
非同期の典型フローに関して - 非同期的なコードは結果の予測をしにくい場面が多い - 呼ぶ → 返る → 何かが起きる など
非同期テストの課題 - 例えば、context.WithDeadlineを使用したテスト - キャンセルされるかのテストをしたい
非同期テストの課題 - 1秒後にタイムアウトする contextを作成 - timeoutまで待つ - その後、コンテキストがキャンセルされているか確認する
非同期テストの課題 - 1秒後にタイムアウトする contextを作成 - timeoutまで待つ - その後、コンテキストがキャンセルされているか確認する 成功してい る!
非同期テストの課題 - 1秒後にタイムアウトする contextを作成 - timeoutまで待つ - その後、コンテキストがキャンセルされているか確認する あれ、何回か やったら失敗
してる・・
非同期テストの課題 - 成功したり、失敗したり不安定な挙動になってしまう
非同期テストの課題 - これに対して、 sleep時間をもう少し長く持つことも可能 - ただ、テストの実行時間は長くなってしまう なんか失敗する から時間を延長 してみよう!
非同期テストの課題 • 安定さは増すがテストの実行時間は遅くなる ◦ Clock型を作成してテストの時間進行を操作等はできるが複雑性は増す • 不安定さは必ずしも消えるとは言い切れない ◦ 実際の時間やスケジューリング等の外部影響を受ける可能性がある ▪
GCによる一時停止 ▪ OSスケジューラの遅延 ▪ システムコールの遅延 ▪ CI環境やチーム開発内等 ▪ etc…なんか失敗するから時間を延長してみよう!
非同期テストの課題 例2:定期的に pollingする - 動画のトランスコード - 動画トランスコードの状態 を定期的に pollingしjobが 完了するまで待つ
- tickの度にトランスコード のjobの状態を確認し、完 了したらHLS配信用を永続 化
非同期テストの課題 例2:定期的に pollingする テストしたいケース : - 実際にtickするたびにどの状態に切り替わるか - tickerの中の全ての動作を網羅的にテストしたい 課題
- 毎tick毎の状態を確認したり、 timeout後のエラー処理などを sleepなどで待 つ必要がありテストで確認しづらい
非同期テストの課題 例3:ゴルーチンの完了を待機する - サムネイル画像の生成 - 動画IDに対してサムネ画像を生成 する - 全てのゴルーチンが完了するまで 待機する
非同期テストの課題 例3:ゴルーチンの完了を待機する テストしたいケース: - 動画のNフレーム目の画像が生成されるか - 画像の生成などを含めて一貫した動作の確認をしたい 課題 - ゴルーチンがいつ完了するかわからない
非同期テストの課題 - 非同期処理のテストにおいて、タイミングや時間を制御するのが難しい。 - 実際のシステムクロックとの同期の機構に依存しているため、複数のGoroutineが並行して動作する ため、実行のタイミングや順序を制御しにくい - これによりテスト毎に異なる結果が生じる可能性があり、安定性の低いテスト(不確実なテスト) となってしまう -
実行タイミングを制御するために、time.Sleepなどを使い無理やり同期させる必要があったり ・・ - テストロジックがTimerの状態を確認する等で複雑になる → 本来のテストのロジックにfocusできな い
非同期テストの課題 - その結果、トレードオフを考えないといけなくなる - シンプルさを保てているか - テストの実行時間を考慮できているか - 安定性を考慮できているか (Flakyなテストではないか)
- テストを安定させるために長い待機時間を入れる → テストが遅くなる - テストを高速化するために待機時間を短くする → テストが不安定になる - テストを安定させるためにテストのための余分なロジックを追加する → シン プルさに欠ける - どちらも同時に達成するのは困難 で、どちらかを犠牲にするトレードオフが発 生する
新たな選択肢 - Go 1.24でexperimentalの状態で synctest というものが現れた - Go 1.25 で正式リリース
非同期テストの課題 例2:定期的に pollingする テストしたいケース : - 実際にtickするたびにどの状態に切り替わるか - tickerの中の全ての動作を網羅的にテストしたい 課題
- 毎tick毎の状態を確認したり、 timeout後のエラー処理などを sleepなどで待 つ必要がありテストで確認しづらい
非同期テストの課題 sleepを使ったテスト synctestを使ったテスト
非同期テストの課題 sleepを使ったテスト(35s) synctestを使ったテスト(0s)
testing/synctest
go 1.24時点のtesting/synctest • Go 1.24時点では実験的にリリースされた新しいパッケージ ◦ 実験的にリリースされているので、GOEXPERIMENT=synctest を 設定する必要があった
Go 1.24時点のtesting/synctest
Go 1.24時点のtesting/synctest RunとWait 二つの APIが提供されてい る
Go 1.25でのtesting/synctest
Go 1.25でのtesting/synctest Go 1.24と Go 1.25での差分に気が付きましたでしょうか?
Go 1.25でのtesting/synctest Go 1.24と Go 1.25での差分に気が付きましたでしょうか?
Go 1.25でのtesting/synctest RunはDeprecatedになりました。 Testを代わりに使 うよう推奨されています。 Run自体は Go 1.26で削除される予定です
なぜRunはdeprecatedになったのか ただ名前を変更しただけでなく、内部も変わっている synctest.Runはtestingパッケージとうまく噛み合っていなかった: - T.Cleanup: bubble内で登録した cleanup がbubble外で実行される。cleanup をbubble内 goroutine
の終了に使うと問題になる - T.Context: T.Context は“bubbleに紐付かない Done”を持つ context を返す ため、bubble内で使いたい挙動ではない
testing/synctestが提供するAPI
testing/synctestが提供するAPI
Test(t *testing.T, f func t(*testing.T))
testing/synctest Test - Test(t, f)は新しいbubbleを作成しfを実行し、bubble内の 全てのゴルーチンの終了またはデッドロック検出までを担当 する - T.Cleanupはbubble内で実行し、T.Context()はbubble内の Contextを返します。
- T.Run や T.Parallel や T.Deadlineをbubble内で呼び出すの は禁止されています
testing/synctest Test - T.Run や T.Parallel や T.Deadlineを bubble内で呼び 出すのは禁止さ
れています go/src/testing/testing.go
testing/synctest Test - T.Run や T.Parallel や T.Deadlineを bubble内で呼び出 すのは禁止されて
います go/src/testing/testing.go
testing/synctest Test - T.Run や T.Parallel や T.Deadlineを bubble内で呼び出 すのは禁止されて
います go/src/testing/testing.go
testing/synctest Test - bubbleの中に存在するゴルーチンは仮想時間を使います - 初期時間はUTC の 2000-01-01
testing/synctest Test - この仮想時間はbubble内の全てのゴルーチンがブロックさ れた時に初めて時間が進みます
testing/synctest Test - Test は関数 f を新しいゴ ルーチンの中で実行する - bubble
構造体を作成し、 root=gp、total=1、 running=1 をセット - gp.bubble=bubbleで呼び 出し元のゴルーチンを bubble内に隔離する src/runtime/synctest.go
testing/synctest Test - Test は関数 f を新しいゴ ルーチンの中で実行する - bubble
構造体を作成し、 root=gp、total=1、 running=1 をセット - gp.bubble=bubbleで呼び 出し元のゴルーチンを bubble内に隔離する src/runtime/synctest.go
testing/synctest Test - newprocでfを実行する新規 ゴルーチン(main)を作成し スケジューリング src/runtime/synctest.go
testing/synctest Test - 次に前進する時間がな い場合やbubble.main のゴルーチンの処理が 終了するまでループさ れます (bubble.done) src/runtime/synctest.go
testing/synctest Test - bubbleの中の全ての ゴルーチンがブロック された時にbubble内 のrootのゴルーチンが 返される src/runtime/synctest.go
testing/synctest Test src/runtime/synctest.go
testing/synctest Test - 次に進む時間を取得 し、その分の時間を進 める src/runtime/synctest.go
testing/synctest Test - bubbleに所属してい る、ゴルーチンは bubble内の仮想時間 を見る src/runtime/time.go
testing/synctest Test
testing/synctest Test まとめ - Test は関数 f を新しいゴルーチンの中で実行する - 新しいゴルーチンとそれから間接的に開始されたゴルーチン
は bubble を形成します - Test は bubble の中のすべてのゴルーチンの終了または Deadlockの検出をするまで待つ - bubbleの中に存在するゴルーチンは仮想時間を使います - 初期時間はUTC の 2000-01-01 - この仮想時間はbubble内のゴルーチンがブロックされた時 に初めて時間が進みます
Wait()
testing/synctest Wait - Wait は、生成されたbubbleにおいて現在のゴルーチン以外 の全てのゴルーチンがDurably Blockedという状態になるま でブロックし続けます。 - 同一bubble内で同時に複数のWaitを呼び出す場合
bubble 外の別ゴルーチンの呼び出しなどはパニックする - bubble 内の他のゴルーチンによってのみブロックを解除で きる場合、そのゴルーチンは Durably Blocked な状態とさ れる
testing/synctest Wait 1: タイマーがnow以前に到 来済みならrootを起こし て、タイマー処理を進める 2: ゴルーチンがWait()によ りブロックされている場合 は起こす
3: bubble内の全てのゴルー チンがdurably blockedなら rootのゴルーチンを返す src/runtime/synctest.go
testing/synctest Wait 1: タイマーがnow以前に到 来済みならrootを起こし て、タイマー処理を進める 2: ゴルーチンがWait()によ りブロックされている場合 は起こす
3: bubble内の全てのゴルー チンがdurably blockedなら rootのゴルーチンを返す src/runtime/synctest.go
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
testing/synctest Wait
Durably Blocked とは
testing/synctest durably blockedの定義 - bubble外のイベントでは解除されないブロック状態 - 例えば: - time.Sleep -
bubble内のchannelに対してのsend/receive - selectのケースがbubble内のchannel - sync.Cond.Wait - など・・
testing/synctest durably blockedの定義 Durably Blockedではないもの: - bubble外のイベントやゴルーチンによって解除されうるも の これによって何が起きるか: -
仮想時間が進まず、panicやタイムアウトで終了する - bubble がidleとみなされず、waitは永遠にブロックされる
testing/synctest non durably blockedの例
testing/synctest Expect: 100-continue
testing/synctest Expect: 100-continue https://pkg.go.dev/testing/synctest#hdr-Example__HTTP_100_Continue
終わりに
まとめ - 隔離性 (Bubble) - 決定性 (Durably Blocked) - 非同期処理のテストにおけるFlakyや実行時間問題の解消
ありがとうございました