Skip to content

Commit e19bafa

Browse files
authored
Merge pull request #940 from devlights/add-synctest-example
Add examples/synctest
2 parents 1e6f5ad + bebe519 commit e19bafa

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

examples/synctest/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# これは何?
2+
3+
Go1.24で実験的機能として追加され、Go1.25で一般提供となった [testing.synctest](https://pkg.go.dev/testing/synctest) パッケージについてのサンプルが配置されています。
4+
5+
## bubble について
6+
7+
**bubble** とは、テスト中に生成したgoroutineやtime.Timerなどのリソースを外部と完全に隔離した実行環境を意味します。
8+
9+
### bubbleの特徴
10+
11+
#### 隔離された実行環境
12+
13+
synctest.Test()で作成されるbubble内で起動したgoroutineや生成したチャネル、タイマーなどは、bubble外から操作できません。たとえば、bubble内で作ったチャネルをbubble外から送受信しようとするとpanicになります。
14+
15+
#### 専用のfake時間
16+
17+
bubble内ではtimeパッケージがfake clock(模擬時計)を使います。時間はbubble内のgoroutineがすべて**durably blocked(耐久ブロック)**状態になったときのみ進みます。つまり、計算処理中は時間が止まり、SleepやTimerの待ちが発生したときのみ時間が進みます。
18+
19+
#### durably blocked(耐久ブロック)
20+
21+
goroutineが「耐久ブロック」状態とは、そのgoroutineがbubble内の他のgoroutineからのみ解放される状態を指します。たとえば、bubble内のチャネルに対する送受信、sync.Cond.Wait、sync.WaitGroup.Wait、time.Sleepなどが該当します。
22+
逆に、ファイルI/OやネットワークI/Oなど、bubble外の要因でブロックされる場合は「耐久ブロック」とはみなされません。
23+
24+
#### テストの安定性と高速化
25+
26+
bubbleを使うことで、goroutineの競合やタイミング依存によるテストの不安定さ(フレーキー)を防ぎつつ、実際には待たずに一瞬でテストが終わるため、高速かつ確実な非同期テストが可能になります。
27+
28+
### bubbleの注意点
29+
30+
#### I/O系のブロックは対象外
31+
32+
ネットワークやファイルI/Oなど、カーネルや外部システムに依存するブロックは「耐久ブロック」とみなされず、bubbleの管理外です。そのため、bubble内でネットワーク通信をテストしたい場合は、fakeネットワーク(例:net.Pipe())を使う必要があります。
33+
34+
#### bubbleのライフサイクル
35+
36+
bubbleのルートgoroutine(Testに渡した関数)が終了すると、それ以降はfake時間も進まなくなり、bubble内のgoroutineがSleepやTimerでブロックしているとデッドロックでテストが失敗します。
37+
38+
39+
## 参考情報
40+
41+
- [synctest package](https://pkg.go.dev/testing/synctest)
42+
- [Testing Time (and other asynchronicities)](https://go.dev/blog/testing-time)
43+
- [Go synctest: Solving Flaky Tests](https://victoriametrics.com/blog/go-synctest/)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- go test . -v
9+
ignore_error: true
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package faketime
2+
3+
import (
4+
"testing"
5+
"testing/synctest"
6+
"time"
7+
8+
"github.com/nalgeon/be"
9+
)
10+
11+
const (
12+
SleepTime = 2 * time.Second
13+
)
14+
15+
func TestNoFakeTime(t *testing.T) {
16+
start := time.Now()
17+
{
18+
// 普通に時間が流れるので指定時間スリープする
19+
time.Sleep(SleepTime)
20+
}
21+
elapsed := time.Since(start)
22+
23+
be.Equal(t, elapsed, SleepTime) // 実際の結果は実行時に変化し、基本的に厳密な一致は出来ないことがほとんど
24+
}
25+
26+
func TestUseFakeTime(t *testing.T) {
27+
synctest.Test(t, func(t *testing.T) {
28+
start := time.Now()
29+
{
30+
// Fake-Timeが使用されるためsynctest内では一瞬で完了する
31+
time.Sleep(SleepTime)
32+
}
33+
elapsed := time.Since(start)
34+
35+
be.Equal(t, elapsed, SleepTime) // Fake-Timeにより結果が一致する
36+
})
37+
}

0 commit comments

Comments
 (0)