こんにちは。Tomoyuki(@tomoyuki65)です。
Go言語(Golang)のバージョン1.25から並行処理・非同期処理テストを高速化するための「testing/synctest」が追加されました。
この記事では、そんなGo言語(Golang)のtesting/synctestの使い方についてご紹介します。
Go言語(Golang)testing/synctestの使い方|Go1.25で並行処理・非同期処理テストを高速化
testing/synctestとは?
「testing/synctest」は、並行処理・非同期処理のテストを決定論的かつ高速に書くための標準パッケージです。
特に次の問題を解決するために作られています。
・goroutineの実行順に依存するテスト
・「5秒待つ」みたいな遅いテスト
・timeout/retry/backoffの検証
また、「synctest」の最大の特徴は以下の通りです。
・goroutineの状態追跡
・bubble(隔離空間)
これにより、「synctest.Test()」の中では実時間ではなく「仮想時間」が進むため、例えば「time.Sleep(1 * time.Hour)」のようなコードがあっても実際には1時間待たず、goroutineが全部ブロック状態になるとsynctest が自動で時間を進めて処理します。
例
例えば以下のようなサンプルコードを例とします。
package main
import (
"fmt"
"time"
)
// ゴルーチンを使った関数
func asyncHello(ch chan<- string) {
go func() {
// 3秒待つ
time.Sleep(3 * time.Second)
// チャネルに文字列を送信
ch <- "hello"
}()
}
func main() {
// チャネルを定義
ch := make(chan string)
// ゴルーチンを使った関数を実行
asyncHello(ch)
// チャネルから文字列を受信
msg := <-ch
// ログ出力
fmt.Println(msg)
}
実行結果としては、3秒後に文字列「hello」が出力されます。
hello
この処理のゴルーチンを使った関数「asyncHello」に関するテストコードを書く際に、Go1.24以前では以下のように書いたりします。
package main
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestOldAsyncHello(t *testing.T) {
// チャネルを定義
ch := make(chan string)
// ゴルーチンを使った関数を実行
asyncHello(ch)
// 検証
select {
// チャネルを受信した場合
case msg := <-ch:
assert.Equal(t, "hello", msg)
// タイムアウト設定
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
}
このテストコードを実行すると、以下のようにテストの完了には3秒かかってしまいます。

次にこのテストコードをGo1.25以上で使える「testing/synctest」を使って修正すると以下のようになります。
package main
import (
"testing"
"testing/synctest"
"time"
"github.com/stretchr/testify/assert"
)
func TestAsyncHello(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
// チャネルを定義
ch := make(chan string)
// ゴルーチンを使った関数を実行
asyncHello(ch)
// 検証
select {
// チャネルを受信した場合
case msg := <-ch:
assert.Equal(t, "hello", msg)
// タイムアウト設定
case <-time.After(5 * time.Second):
t.Fatal("timeout")
}
})
}
このテストコードを実行すると以下のようにすぐに完了するため、テストコードの高速化が可能です。

synctestが有効かの判断について
synctestは、Goの中だけで閉じた並行処理・非同期処理を決定論的に進められる仕組みとなっているため、全ての並行処理・非同期処理に有効なわけではありません。
そのため、synctestが有効かの判断については主に以下のようになります。
・「time / channel / sync / context」だけで完結するような処理
→ synctestが有効
・「ネットワーク / DB / ファイル / OS / 外部プロセス」が絡むような処理
→ 「mock / stub / interface」化などが必要
最後に
今回はGo言語(Golang)のtesting/synctestの使い方についてご紹介しました。
synctestが有効であればテストコードの高速化が図れるため、CI(Continuous Integration:継続的インテグレーション)の処理時間を短縮して効率化を図れる可能性があります。
Goのバージョン1.25以上を利用する際は、ぜひ参考にしてみて下さい。


コメント