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

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

このコミットは、Go言語の標準ライブラリ testing パッケージに、ベンチマークテストの実行回数を調整する roundDown10 関数に関連するバグ(Issue 5599)をテストするための新しいテストケースを追加するものです。具体的には、src/pkg/testing/benchmark_test.goTestRoundDown10 というテスト関数と、そのテストデータ roundDownTests が追加され、src/pkg/testing/export_test.go には内部関数 roundDown10 をテスト可能にするためのエクスポートが追加されています。

コミット

commit 787976c73936e5e19535872f570116a6852c7c6a
Author: Dave Cheney <dave@cheney.net>
Date:   Fri May 31 23:03:22 2013 +1000

    testing: add test for issue 5599
    
    Update #5599
    
    R=golang-dev, r, minux.ma
    CC=golang-dev
    https://golang.org/cl/9738052

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

https://github.com/golang/go/commit/787976c73936e5e19535872f570116a6852c7c6a

元コミット内容

testing: add test for issue 5599

Update #5599

R=golang-dev, r, minux.ma
CC=golang-dev
https://golang.org/cl/9738052

変更の背景

このコミットの背景には、Go言語の testing パッケージにおけるベンチマークテストの実行回数調整に関するバグ、具体的には Issue 5599: testing: Benchmark.N can be 0 が存在します。

Goのベンチマークテストは、testing.B 型の N フィールドを使用して、テスト対象の操作を何回繰り返すかを決定します。N の値は、ベンチマークの実行時間に基づいて動的に調整され、通常は10の倍数に丸められます。この丸め処理を行う内部関数が roundDown10 です。

Issue 5599では、roundDown10 関数が特定の入力値(例えば101や1001)に対して期待される結果(それぞれ100や1000)を返さず、誤った丸め処理を行う可能性が指摘されました。これにより、ベンチマークの実行回数が意図せず0になるなど、ベンチマークテストの信頼性に影響を与える可能性がありました。

このコミットは、この roundDown10 関数のバグを再現し、修正を検証するためのテストケースを追加することを目的としています。テストケースを追加することで、将来的な回帰を防ぎ、関数の正確性を保証します。

前提知識の解説

Go言語の testing パッケージ

Go言語には、ユニットテスト、ベンチマークテスト、サンプルテストをサポートするための組み込みの testing パッケージがあります。

  • ユニットテスト: TestXxx という形式の関数で記述され、コードの個々の単位が正しく動作するかを検証します。
  • ベンチマークテスト: BenchmarkXxx という形式の関数で記述され、コードのパフォーマンスを測定します。testing.B 型の引数を受け取り、b.N 回のループ内でテスト対象の操作を実行します。b.N は、ベンチマークの実行時間に基づいて自動的に調整されます。
  • testing.B.N: ベンチマーク関数内で実行されるループの繰り返し回数を示します。Goのテストフレームワークは、ベンチマークが少なくとも1秒間実行されるように b.N の値を調整します。この調整プロセスには、実行回数を10の倍数に丸める処理が含まれます。

roundDown10 関数

testing パッケージの内部には、ベンチマークの実行回数 N を調整するために使用される roundDown10 という関数が存在します。この関数の目的は、与えられた数値を10の累乗(1, 10, 100, 1000など)に切り下げることです。例えば、roundDown10(123)100 を返し、roundDown10(9)1 を返します。これは、ベンチマークの実行回数を「きれいな」数値に保ち、結果の解釈を容易にするためです。

Goのテスト慣習と _test.go ファイル

Goでは、テストコードは通常、テスト対象のソースファイルと同じディレクトリに _test.go というサフィックスを持つファイルとして配置されます。

  • package <name>: 通常のテストファイルは、テスト対象のパッケージと同じパッケージ名を使用します。これにより、テストコードはテスト対象のパッケージの内部関数や変数にアクセスできます。
  • package <name>_test: ベンチマークテストや、テスト対象のパッケージの外部から見た振る舞いをテストしたい場合は、_test サフィックスを付けたパッケージ名を使用します。これにより、テストコードはテスト対象のパッケージをインポートして使用することになり、外部APIのみにアクセスできます。

export_test.go ファイル

Goでは、パッケージの内部関数や変数は通常、外部から直接アクセスできません。しかし、テスト目的で内部関数にアクセスしたい場合があります。このような場合、Goのテストフレームワークは export_test.go という特別なファイル名パターンを認識します。このファイル内で、内部関数や変数をパッケージレベルの変数に代入することで、テストパッケージからそれらにアクセスできるようになります。これは、テストのためだけにAPIを公開するものであり、通常のアプリケーションコードからは使用されません。

技術的詳細

このコミットは、roundDown10 関数の正確性を検証するために、以下の技術的アプローチを取っています。

  1. roundDown10 関数のエクスポート: src/pkg/testing/export_test.go ファイルが新規作成され、testing パッケージの内部関数 roundDown10RoundDown10 という名前でエクスポートされています。

    package testing
    
    var RoundDown10 = roundDown10
    

    これにより、testing_test パッケージ(つまり、testing パッケージの外部からテストを行うパッケージ)から testing.RoundDown10 としてこの関数を呼び出すことが可能になります。これは、内部関数のテストを可能にするための標準的なGoのテストパターンです。

  2. 新しいベンチマークテストファイルの追加: src/pkg/testing/benchmark_test.go ファイルが新規作成され、testing_test パッケージの一部として定義されています。

    package testing_test
    
    import (
    	"testing"
    )
    

    このファイルは、testing パッケージをインポートし、そのエクスポートされた関数 RoundDown10 を使用してテストを行います。

  3. テストデータの定義: benchmark_test.go 内に、roundDownTests という構造体のスライスが定義されています。これは、roundDown10 関数に与える入力値 v と、それに対して期待される出力値 expected のペアを保持します。

    var roundDownTests = []struct {
    	v, expected int
    }{
    	{1, 1},
    	{9, 1},
    	{10, 1},
    	{11, 10},
    	{100, 10},
    	//	{101, 100}, // issue 5599
    	{1000, 100},
    	//	{1001, 1000}, // issue 5599
    }
    

    注目すべきは、コメントアウトされた {101, 100}{1001, 1000} の行です。これらは、Issue 5599で指摘された、roundDown10 が正しく動作しない可能性のある具体的な入力値と、その期待される結果を示しています。テストが追加された時点では、これらのケースはまだ失敗する可能性があったため、コメントアウトされています。これは、バグが修正された後にコメントを解除してテストを有効にするためのプレースホルダーとして機能します。

  4. テスト関数の実装: TestRoundDown10 というテスト関数が実装されています。この関数は roundDownTests スライスをイテレートし、各テストケースに対して testing.RoundDown10 を呼び出し、実際の結果が期待される結果と一致するかを検証します。

    func TestRoundDown10(t *testing.T) {
    	for _, tt := range roundDownTests {
    		actual := testing.RoundDown10(tt.v)
    		if tt.expected != actual {
    			t.Errorf("roundDown10: expected %v, actual %v", tt.expected, actual)
    		}
    	}
    }
    

    もし期待される結果と実際の結果が異なる場合、t.Errorf を使用してエラーメッセージを出力し、テストを失敗させます。

このコミットは、特定のバグをターゲットにしたテスト駆動開発(TDD)のアプローチを示しています。まずバグを再現するテストケースを作成し、そのテストが失敗することを確認します。その後、バグを修正するコードを書き、テストが成功することを確認します。これにより、バグが修正されたことを保証し、将来的な回帰を防ぐことができます。

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

このコミットによるコードの変更は以下の2つのファイルに集中しています。

  1. src/pkg/testing/benchmark_test.go (新規追加)

    • roundDownTests というテストデータスライスの定義。
    • TestRoundDown10 というテスト関数の定義。
  2. src/pkg/testing/export_test.go (新規追加)

    • testing パッケージの内部関数 roundDown10RoundDown10 としてエクスポートする行。

src/pkg/testing/benchmark_test.go

--- /dev/null
+++ b/src/pkg/testing/benchmark_test.go
@@ -0,0 +1,31 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package testing_test
+
+import (
+	"testing"
+)
+
+var roundDownTests = []struct {
+	v, expected int
+}{
+	{1, 1},
+	{9, 1},
+	{10, 1},
+	{11, 10},
+	{100, 10},
+	//	{101, 100}, // issue 5599
+	{1000, 100},
+	//	{1001, 1000}, // issue 5599
+}
+
+func TestRoundDown10(t *testing.T) {
+	for _, tt := range roundDownTests {
+		actual := testing.RoundDown10(tt.v)
+		if tt.expected != actual {
+			t.Errorf("roundDown10: expected %v, actual %v", tt.expected, actual)
+		}
+	}
+}

src/pkg/testing/export_test.go

--- /dev/null
+++ b/src/pkg/testing/export_test.go
@@ -0,0 +1,7 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package testing
+
+var RoundDown10 = roundDown10

コアとなるコードの解説

src/pkg/testing/benchmark_test.go

このファイルは、testing パッケージのベンチマーク関連機能、特に roundDown10 関数の動作を検証するためのテストケースを定義しています。

  • package testing_test: この行は、このファイルが testing パッケージの外部テストであることを示しています。これにより、testing パッケージをインポートして、そのエクスポートされた機能(この場合は testing.RoundDown10)のみを使用できます。これは、パッケージの公開APIが正しく動作するかを検証する標準的な方法です。
  • var roundDownTests = []struct { v, expected int }: これは、テーブル駆動テスト(Table Driven Tests)の典型的なパターンです。vroundDown10 関数への入力値、expected はその入力に対する期待される出力値を表します。
    • {1, 1}, {9, 1}, {10, 1}: 10未満の数値や10自体が正しく1に丸められることをテストします。
    • {11, 10}, {100, 10}: 10より大きく100未満の数値が正しく10に丸められることをテストします。
    • {1000, 100}: 1000が正しく100に丸められることをテストします。
    • // {101, 100}, // issue 5599// {1001, 1000}, // issue 5599: これらは、Issue 5599で報告された具体的なバグのケースです。これらの行がコメントアウトされているのは、コミット時点ではまだバグが修正されておらず、テストが失敗する可能性があるためです。バグが修正された後、これらのコメントを解除してテストを有効にすることで、回帰テストとして機能します。
  • func TestRoundDown10(t *testing.T): これはGoのテスト関数です。t *testing.T 引数は、テストの実行中にエラーを報告したり、テストの状態を制御したりするためのメソッドを提供します。
    • for _, tt := range roundDownTests: roundDownTests スライス内の各テストケースをループ処理します。
    • actual := testing.RoundDown10(tt.v): エクスポートされた RoundDown10 関数を呼び出し、実際の結果を取得します。
    • if tt.expected != actual: 実際の結果が期待される結果と異なる場合、エラーを報告します。
    • t.Errorf("roundDown10: expected %v, actual %v", tt.expected, actual): テストが失敗したことを示し、期待値と実際の値を含む詳細なエラーメッセージを出力します。

src/pkg/testing/export_test.go

このファイルは、testing パッケージの内部関数をテスト目的で外部に公開するために使用されます。

  • package testing: このファイルは testing パッケージの一部として定義されています。
  • var RoundDown10 = roundDown10: この行がこのファイルの核心です。testing パッケージ内で定義されている非エクスポート(小文字で始まる)関数 roundDown10 を、エクスポートされた(大文字で始まる)変数 RoundDown10 に代入しています。これにより、testing パッケージをインポートする他のパッケージ(例: testing_test パッケージ)から testing.RoundDown10 としてこの内部関数にアクセスできるようになります。これは、Goで内部関数をテストするための一般的なイディオムです。

これらの変更により、roundDown10 関数の正確性を体系的に検証するための基盤が確立され、Issue 5599のようなバグの再発を防ぐための重要なステップとなります。

関連リンク

参考にした情報源リンク