Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11927] ファイルの概要

コミット

commit 9a445600334fcd4e856206b0223f8b85c71f7999
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Wed Feb 15 14:56:47 2012 +1100

    time: run TestTicker for longer during short test, otherwise it fails
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5671049

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/9a445600334fcd4e856206b0223f8b85c71f7999

元コミット内容

time: run TestTicker for longer during short test, otherwise it fails

このコミットは、Go言語の標準ライブラリtimeパッケージ内のTestTickerテストが、go test -shortフラグを付けて実行された場合に失敗する問題を修正するためのものです。具体的には、testing.Short()trueの場合にTestTickerが使用するDelta(ティック間隔)の値を10 * Millisecondから20 * Millisecondに倍増させることで、テストの安定性を向上させています。

変更の背景

Go言語のテストフレームワークには、go test -shortというフラグがあります。これは、開発者が時間のかかるテストやリソースを大量に消費するテストをスキップし、より迅速なテスト実行を可能にするためのものです。テストコード内でtesting.Short()関数を呼び出すことで、このフラグが設定されているかどうかを判定し、テストの動作を調整できます。

timeパッケージのTestTickerは、time.Tickerの機能が正しく動作するかを検証するテストです。time.Tickerは、指定された間隔でイベントを定期的に発生させるためのメカニズムを提供します。このテストでは、Count回イベントが発生するのを待ち、その間隔が期待通りであることを検証します。

問題は、go test -shortモードで実行された際に、TestTickerDeltaの値を10 * Millisecondに設定していたことにありました。この10ミリ秒という非常に短い間隔は、テストが実行される環境(CPUの負荷、スケジューリング、システムコールにかかる時間など)によっては、正確なティックを検出するには短すぎる場合がありました。結果として、テストが期待されるCount回のティックを時間内に受け取れず、テストが不安定になったり、ランダムに失敗したりする「flaky test(不安定なテスト)」の状態に陥っていました。

このコミットは、go test -shortモードでのTestTickerの信頼性を向上させることを目的としています。Deltaの値を20ミリ秒に増やすことで、テストがより多くの猶予を持ち、様々な実行環境下でも安定してパスするように調整されています。

前提知識の解説

1. time.Ticker

Go言語のtimeパッケージは、時間に関連する機能を提供します。その中でもtime.Tickerは、一定の間隔でイベントを繰り返し発生させるための構造体です。

  • time.NewTicker(d Duration): 指定された期間dごとにティックを送信する新しいTickerを作成します。
  • Ticker.C <-chan Time: Ticker構造体にはCという名前のチャネルが含まれています。このチャネルは、ティックが発生するたびに現在の時刻を送信します。ユーザーはこのチャネルを読み取ることで、定期的なイベントを処理できます。
  • Ticker.Stop(): Tickerの使用が終わったら、必ずStop()メソッドを呼び出してリソースを解放する必要があります。これを怠ると、関連するゴルーチンがリークする可能性があります。

time.Tickerは、例えば定期的なログの出力、キャッシュの更新、ポーリング処理など、一定間隔で処理を実行したい場合に非常に便利です。

2. testing.Short()

Go言語のテストフレームワーク(testingパッケージ)には、テストの実行を制御するための便利な機能がいくつかあります。その一つがtesting.Short()関数です。

  • 目的: testing.Short()は、go testコマンドが-shortフラグ付きで実行された場合にtrueを返します。この機能は、開発者が時間のかかるテストや、外部リソース(ネットワーク、データベースなど)に依存するテストを、通常の開発サイクルではスキップし、CI/CDパイプラインなどのより包括的なテスト実行時にのみ実行できるようにするために使用されます。
  • 使用例:
    func TestLongRunningOperation(t *testing.T) {
        if testing.Short() {
            t.Skip("skipping test in short mode.")
        }
        // 時間のかかるテストロジック
    }
    
    このように記述することで、go test -shortと実行した際にはTestLongRunningOperationはスキップされ、go testと実行した際には通常通り実行されます。
  • テスト動作の変更: testing.Short()は、テストをスキップするだけでなく、テストの動作自体を「ショートモード」用に変更するためにも使用できます。例えば、テストが実際の外部サービスと通信する代わりに、モックデータや簡略化されたセットアップを使用するように切り替えることができます。今回のコミットは、この「テスト動作の変更」の典型的な例であり、テストのタイミングパラメータを調整しています。

3. Goテストにおけるタイミングの問題

Goのテスト、特に並行処理や時間に関連するテストでは、タイミングの問題(flaky tests)が発生しやすいことがあります。

  • time.Sleepの信頼性の低さ: テスト内でtime.Sleepを使用してゴルーチンの完了やイベントの発生を待つことは、非常に信頼性が低いです。実際のスリープ時間は、システムスケジューラ、OSの違い、システム負荷などによって変動する可能性があり、テスト結果の一貫性を損ないます。
  • 競合状態(Race Conditions): 複数のゴルーチンが同じ変数に同時にアクセスし、少なくとも1つが書き込みである場合に発生します。これはテストの不安定性の主要な原因の一つです。Goにはgo test -raceという競合検出ツールがあり、これを使用することで競合状態を特定できます。
  • 非決定的な動作: マップのイテレーション順序が非決定的であるなど、Go言語の特定の動作がテストの非決定性を引き起こすことがあります。
  • 解決策:
    • time.Sleepの代わりに、sync.WaitGroup、チャネル、sync.Condなどの適切な同期メカニズムを使用して、ゴルーチンの完了やイベントの発生を待つべきです。
    • Go 1.24以降では、実験的なtesting/synctestパッケージが導入され、時間依存のテストをより決定的に、かつ高速に実行できるようになりました。
    • テストが時間測定機能を検証する場合、実際の経過時間に基づいて上限と下限を計算し、その範囲内であることをアサートします。
    • テストのタイムアウトは、go test -timeoutフラグで設定できますが、これはテストロジックの一部としてではなく、安全策として使用すべきです。

今回のコミットは、まさにこの「テストにおけるタイミングの問題」に対処するものであり、特にtesting.Short()モードでのテストの安定性を確保するために、テストのタイミングパラメータを調整しています。

技術的詳細

TestTickerは、time.Tickerが正確な間隔でティックを生成するかどうかを検証します。テストのロジックは以下のようになっています。

  1. Count(10回)のティックを期待します。
  2. Deltaというティック間隔を定義します。
  3. testing.Short()trueの場合(つまり、go test -shortで実行された場合)、Deltaの値を短く設定します。
  4. NewTicker(Delta)Tickerを作成します。
  5. Count回、ticker.Cチャネルからティックを受信します。
  6. ティックを受信するたびに、前回のティックからの経過時間を測定し、それがDeltaの期待される範囲内にあることを検証します。
  7. 全てのティックを受信した後、ticker.Stop()を呼び出してTickerを停止します。

変更前のコードでは、testing.Short()trueの場合にDelta10 * Millisecondに設定されていました。これは非常に短い時間であり、特にテストが実行される環境の負荷が高い場合や、システムコールやゴルーチンのスケジューリングにわずかな遅延が生じた場合に、テストが期待される10ミリ秒間隔で正確にティックを受信できない可能性がありました。

例えば、テストが10ミリ秒のティックを期待しているにもかかわらず、システムが11ミリ秒かかって次のティックを生成した場合、テストは失敗します。これは、テストが厳密すぎるか、またはテスト環境が不安定であるために発生します。

このコミットでは、Delta20 * Millisecondに倍増させることで、テストがより寛容になります。これにより、システムがティックを生成するのにわずかに時間がかかったとしても、テストが失敗する可能性が低減されます。これは、テストのロバスト性(堅牢性)を高め、環境依存の不安定なテスト(flaky test)を減らすための一般的なアプローチです。テストの目的は機能の正確性を検証することであり、極端なタイミングの正確性を検証することではない場合、このような調整は適切です。

コアとなるコードの変更箇所

--- a/src/pkg/time/tick_test.go
+++ b/src/pkg/time/tick_test.go
@@ -13,7 +13,7 @@ func TestTicker(t *testing.T) {
 	const Count = 10
 	Delta := 100 * Millisecond
 	if testing.Short() {
-		Delta = 10 * Millisecond
+		Delta = 20 * Millisecond
 	}
 	ticker := NewTicker(Delta)
 	t0 := Now()

コアとなるコードの解説

このコミットの変更は、src/pkg/time/tick_test.goファイル内のTestTicker関数にあります。

元のコードでは、testing.Short()trueの場合、つまりgo test -shortフラグが指定されている場合に、Delta変数が10 * Millisecondに設定されていました。

 	if testing.Short() {
 		Delta = 10 * Millisecond
 	}

この変更により、Deltaの値が20 * Millisecondに修正されました。

 	if testing.Short() {
-		Delta = 10 * Millisecond
+		Delta = 20 * Millisecond
 	}

この変更の意図は、go test -shortモードでTestTickerが実行される際のティック間隔を長くすることです。10ミリ秒という短い間隔では、テストが実行される環境の特性(例えば、CPUのスケジューリングの遅延、システムコールのオーバーヘッドなど)によって、期待されるティックが正確なタイミングで発生しないことがありました。これにより、テストが不安定になり、ランダムに失敗する原因となっていました。

Delta20ミリ秒に増やすことで、各ティック間の許容される誤差が実質的に大きくなります。これにより、テストはより多くの猶予を持ち、わずかなシステム遅延があっても安定してパスするようになります。これは、テストの信頼性を向上させ、開発者がテスト結果に一貫性を持って信頼できるようにするための重要な調整です。

関連リンク

参考にした情報源リンク