[インデックス 12424] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtime
パッケージ内のtick_test.go
ファイルに対する変更です。具体的には、TestTicker
というテスト関数において、go test -short
フラグが指定された「ショートテスト」実行時に、time.Ticker
の動作が期待よりも遅延した場合でもテストが失敗しないように修正されています。これにより、開発時の迅速なテスト実行におけるテストの不安定性(flakiness)が軽減されます。
コミット
commit db80edde7ddd144388e367c9a8328121c98330ce
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Wed Mar 7 01:10:55 2012 +0800
time: during short test, do not bother tickers take longer than expected
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5752058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/db80edde7ddd144388e367c9a8328121c98330ce
元コミット内容
このコミットは、time
パッケージのtick_test.go
ファイルにおいて、TestTicker
関数内のアサーションロジックを修正しています。元のコードでは、time.Ticker
が生成するティックの間隔が期待される範囲(target-slop
からtarget+slop
)を外れた場合にテストが失敗していました。
変更の背景
Go言語のテストフレームワークには、テストの実行時間を短縮するための-short
フラグが存在します。このフラグが指定されると、testing.Short()
関数がtrue
を返します。通常、開発者はこのフラグを使用して、CI/CDパイプラインでのフルテスト実行とは別に、ローカルでの迅速な開発サイクル中に時間のかかるテストをスキップします。
time.Ticker
のような時間ベースのテストは、実行環境の負荷、OSのスケジューリング、ガベージコレクションの発生など、様々な外部要因によってそのタイミングが微妙に変動する可能性があります。特に、期待よりも時間がかかった場合(dt > target+slop
)にテストが失敗するケースは、これらの外部要因によってテストが不安定(flaky)になる原因となりがちでした。
このコミットの背景には、ショートテストの目的は基本的な機能の検証であり、厳密なタイミング精度を保証することではない、という考えがあります。厳密なタイミングテストは、フルテスト実行時にのみ行われるべきであり、ショートテストでは多少の遅延は許容することで、開発者の生産性を向上させ、テストの信頼性を高めることが目的です。
前提知識の解説
Go言語のtesting
パッケージと-short
フラグ
Go言語には、標準でテストを記述・実行するためのtesting
パッケージが提供されています。
go test
: Goのテストを実行するためのコマンドです。testing.T
: 各テスト関数に渡される構造体で、テストの失敗報告、ログ出力、ヘルパー関数の呼び出しなど、テスト実行に関する様々な機能を提供します。testing.Short()
: この関数は、go test
コマンドに-short
フラグが渡された場合にtrue
を返します。開発者はこの関数を使って、時間のかかるテストやリソースを大量に消費するテストを条件付きでスキップすることができます。例えば、データベース接続を必要とするテストや、ネットワーク通信を伴うテストなどで利用されます。
Go言語のtime
パッケージとtime.Ticker
time
パッケージ: 日付と時刻に関する機能を提供するGoの標準パッケージです。time.Duration
: 時間の長さを表す型です。ナノ秒単位で表現され、time.Second
、time.Millisecond
などの定数を使って期間を指定できます。time.Ticker
: 定期的にイベントを発生させるためのメカニズムを提供します。time.NewTicker(d Duration)
で作成され、指定された期間d
ごとにチャネルに値を送信します。これにより、一定間隔で処理を実行するようなアプリケーションやテストで利用されます。
テストの不安定性(Flakiness)
ソフトウェアテストにおける「不安定性(flakiness)」とは、コードの変更がないにもかかわらず、テストが成功したり失敗したりする現象を指します。これは、テストが外部要因(実行環境の負荷、ネットワークの遅延、並行処理のタイミング、乱数の初期化など)に依存している場合に発生しやすく、開発者の信頼を損ない、CI/CDパイプラインの効率を低下させる大きな問題となります。時間ベースのテストは、特にこの不安定性の影響を受けやすい傾向があります。
技術的詳細
このコミットの技術的な核心は、Goのテストフレームワークが提供するtesting.Short()
関数を、時間ベースのテストの許容範囲を動的に調整するために利用している点にあります。
元のコードでは、TestTicker
関数はtime.Ticker
の動作を検証しており、実際の経過時間dt
が期待される時間target
から許容誤差slop
を引いた値よりも小さいか、またはtarget
にslop
を加えた値よりも大きい場合にテストを失敗させていました。
if dt < target-slop || dt > target+slop {
t.Fatalf(...)
}
この条件式は、dt
が期待される時間範囲[target-slop, target+slop]
の外にある場合に真となります。
変更後のコードでは、dt > target+slop
という条件に!testing.Short()
という条件が追加されました。
if dt < target-slop || (!testing.Short() && dt > target+slop) {
t.Fatalf(...)
}
この変更により、条件式は以下のように解釈されます。
-
dt < target-slop
: 実際の経過時間dt
が期待される最小値よりも短い場合。この条件は常に評価され、ショートテストかどうかにかかわらず、テストは失敗します。これは、Ticker
が期待よりも早くティックを生成することは通常ありえないため、もし発生すればそれはバグである可能性が高いからです。 -
(!testing.Short() && dt > target+slop)
:testing.Short()
がtrue
の場合(go test -short
実行時):!testing.Short()
はfalse
となります。論理AND演算子&&
の短絡評価(short-circuit evaluation)により、dt > target+slop
の部分は評価されません。結果として、dt > target+slop
の条件は無視され、テストはdt
が期待よりも長かったとしても失敗しません。testing.Short()
がfalse
の場合(通常のgo test
実行時):!testing.Short()
はtrue
となります。この場合、dt > target+slop
の条件が評価されます。もしdt
が期待される最大値よりも長ければ、この条件が真となり、テストは失敗します。
このロジックにより、ショートテスト実行時には、Ticker
が期待よりも遅延した場合のチェックがスキップされ、テストの不安定性が解消されます。一方で、フルテスト実行時には、厳密なタイミング精度が引き続き検証されます。これは、テストの目的と実行コンテキストに応じて、適切な厳密さを適用する賢明なアプローチと言えます。
コアとなるコードの変更箇所
変更はsrc/pkg/time/tick_test.go
ファイルの一箇所のみです。
--- a/src/pkg/time/tick_test.go
+++ b/src/pkg/time/tick_test.go
@@ -22,7 +22,7 @@ func TestTicker(t *testing.T) {
dt := t1.Sub(t0)
target := Delta * Count
slop := target * 2 / 10
- if dt < target-slop || dt > target+slop {
+ if dt < target-slop || (!testing.Short() && dt > target+slop) {
t.Fatalf("%d %s ticks took %s, expected [%s,%s]", Count, Delta, dt, target-slop, target+slop)
}
// Now test that the ticker stopped
コアとなるコードの解説
変更された行は、TestTicker
関数内のif
文の条件式です。
dt := t1.Sub(t0)
: テスト開始時刻t0
から終了時刻t1
までの実際の経過時間dt
を計算しています。target := Delta * Count
:Ticker
が生成するティックの期間Delta
とティックの回数Count
から、期待される合計経過時間target
を計算しています。slop := target * 2 / 10
: 許容される誤差範囲slop
を計算しています。ここでは、target
の20%が許容誤差として設定されています。if dt < target-slop || (!testing.Short() && dt > target+slop)
:dt < target-slop
: 実際の経過時間dt
が、期待される最小時間target-slop
よりも短い場合。これは、Ticker
が期待よりも早く動作した場合を検知します。この条件は常に評価されます。(!testing.Short() && dt > target+slop)
: 実際の経過時間dt
が、期待される最大時間target+slop
よりも長い場合。これは、Ticker
が期待よりも遅く動作した場合を検知します。この条件は、testing.Short()
がfalse
(つまり、ショートテストではない)の場合にのみ評価されます。
この修正により、go test -short
実行時には、Ticker
が期待よりも遅延してもテストは失敗しなくなります。これにより、開発時のテストの安定性が向上し、CI/CDなどでのフルテスト実行時には引き続き厳密なタイミングチェックが行われるようになります。
関連リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語の
time
パッケージのドキュメント: https://pkg.go.dev/time - Goのコードレビューシステム(Gerrit)の変更セット: https://golang.org/cl/5752058
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のテストに関する一般的なプラクティスと
testing.Short()
の利用例 - ソフトウェアテストにおける「flakiness」に関する一般的な情報