導入:なぜテストが「たまに」落ちるのか?
Goで開発をしていると、「ローカルでは通るのに、CI環境ではたまにテストが落ちる」という経験はありませんか?このように、実行するたびに結果が変わってしまう不安定なテストのことを「Flaky(フレイキー)テスト」と呼びます。放置するとチームの信頼を損ない、本当に重要なバグを見逃す原因になります。今回は、Goの標準機能を使ってこの厄介なバグを効率的に炙り出す方法を紹介します。
基礎知識:Flakyテストとは何か
Flakyテストは、主に「並行処理(Goroutine)のタイミング依存」や「外部リソース(DBやAPI)の通信遅延」が原因で発生します。例えば、Goroutineの終了を待たずに検証を行ったり、共有変数の更新タイミングが実行順序によって変わったりする場合です。これらは1回実行しただけでは再現しにくいため、確率的に顕在化させるアプローチが不可欠です。
実装/解決策:go test -count=10の活用
Goのテストコマンドには、テストを繰り返し実行するオプションが用意されています。これを利用して、特定のテストをあえて何度も連続実行することで、隠れた非決定的なバグを強制的に引き出します。
サンプルプログラム:再現性を検証するためのコード
以下のコードは、並行処理で共有変数を操作する際にバグが発生しやすい例です。これに対してテストを実行し、Flakyな挙動を検知してみましょう。
// counter.go
package main
// 共有カウンタをインクリメントする関数
func Increment(count int) {
count = count + 1
}
// counter_test.go
package main
import (
“sync”
“testing”
)
func TestIncrement(t testing.T) {
count := 0
var wg sync.WaitGroup
// 100個のGoroutineで同時にインクリメントしようとする
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
Increment(&count)
}()
}
wg.Wait()
// 期待値は100だが、競合が発生すると100未満になることがある
if count != 100 {
t.Errorf("期待値は100ですが、実際は %d です", count)
}
}
実行コマンド:
ターミナルで以下のコマンドを実行してください。
go test -count=10 -v .
このコマンドを実行すると、テストが10回繰り返されます。もしコードに競合があれば、10回の中で一度でも「FAIL」が出るはずです。
応用・注意点:現場で役立つアドバイス
- Race Detectorの併用: テストを繰り返すだけでなく、go test -race -count=10 とすることで、データ競合をGoが検知してエラーを投げてくれます。並行バグの発見には最強の組み合わせです。
- キャッシュの無効化: -count=10 を付けると自動的にテストキャッシュが無効化され、毎回必ず実行されます。
- 根本解決を優先: Flakyテストを見つけたら、単にテストをスキップするのではなく、sync.Mutexやchannelを使って「タイミング依存」を「同期的な構造」へ修正することが重要です。
不安定なテストは開発の敵です。ぜひこの手法を取り入れて、堅牢なGoアプリケーションを構築してください。

コメント