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

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

このコミットは、Go言語のテストツール go test に新しいフラグ -test.example を導入し、例示関数 (Example functions) の実行をより細かく制御できるようにするものです。これにより、ユーザーは正規表現を用いて特定の例示関数のみを実行できるようになります。また、既存の -test.run フラグが設定されている場合には、デフォルトで例示関数が実行されないように挙動が変更されました。

コミット

  • コミットハッシュ: 5876b4eb2881336c9e7007c957002d15ef54a190
  • Author: Rob Pike r@golang.org
  • Date: Mon Feb 27 12:49:10 2012 +1100
  • コミットメッセージ:
    testing: add -test.example flag to control execution of examples
    Also, don't run examples if -test.run is set.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/5697069
    

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

https://github.com/golang/go/commit/5876b4eb2881336c9e7007c957002d15ef54a190

元コミット内容

testing: add -test.example flag to control execution of examples
Also, don't run examples if -test.run is set.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5697069

変更の背景

Go言語のテストフレームワークでは、ExampleXxx という形式の関数を記述することで、コードの利用例をドキュメントとして提供しつつ、それが正しく動作するかをテストすることが可能です。これまでの go test コマンドの挙動では、特に指定がない限りすべての例示関数が実行されていました。

しかし、大規模なプロジェクトや、特定のテストケースに集中してデバッグを行いたい場合など、例示関数の実行を制御したいというニーズがありました。特に、-test.run フラグを使用して通常のテスト関数を正規表現でフィルタリングしている場合でも、例示関数は常に実行されてしまうという問題がありました。

このコミットは、以下の2つの主要な課題を解決するために導入されました。

  1. 例示関数の選択的実行: ユーザーが特定の例示関数のみを実行できるように、正規表現によるフィルタリング機能を提供すること。
  2. -test.run との連携: -test.run が指定されている場合に、例示関数がデフォルトで実行されないようにすることで、テストの実行範囲をより明確にすること。これにより、開発者は通常のテストに集中しやすくなります。

前提知識の解説

go test コマンド

go test はGo言語に組み込まれているテストツールです。Goのソースコード内で定義されたテスト関数 (func TestXxx(*testing.T))、ベンチマーク関数 (func BenchmarkXxx(*testing.B))、そして例示関数 (func ExampleXxx()) を自動的に発見し、実行します。

例示関数 (Example functions)

Goの例示関数は、func ExampleXxx() の形式で定義され、通常はパッケージのドキュメントにコード例として表示されます。これらの関数は、go test コマンドによって実行され、関数内のコメントに記述された期待される出力 (// Output:) と実際の出力が一致するかどうかを検証することで、コード例が常に最新かつ正確であることを保証します。これは、ドキュメントとコードの乖離を防ぐ上で非常に強力な機能です。

正規表現 (Regular Expressions)

正規表現は、文字列のパターンを記述するための強力な言語です。このコミットでは、例示関数の名前をパターンマッチングするために使用されます。例えば、ExampleHelloExampleWorld といった例示関数がある場合、-test.example Hello と指定すれば ExampleHello のみが実行され、-test.example ".*" と指定すればすべての例示関数が実行されます。

testing パッケージ

testing パッケージはGoの標準ライブラリの一部であり、テスト、ベンチマーク、例示関数を記述するための基本的な機能を提供します。go test コマンドは内部的にこのパッケージを利用してテストの実行を管理しています。

技術的詳細

このコミットは、go test コマンドの挙動と、testing パッケージ内の例示関数実行ロジックに以下の変更を加えています。

  1. src/cmd/go/test.go の変更: go test コマンドのヘルプメッセージに、新しく追加される -test.example フラグの説明が追加されました。これにより、ユーザーはこのフラグの存在と機能を知ることができます。説明には、正規表現によるフィルタリング機能と、-test.run が設定されている場合のデフォルト挙動が明記されています。

  2. src/cmd/go/testflag.go の変更: go test コマンドが内部的に testing パッケージに渡すことができるフラグのリストに、-test.example が追加されました。これにより、go test コマンドラインで指定された -test.example の値が、テスト実行時に testing パッケージに正しく伝達されるようになります。

  3. src/pkg/testing/example.go の変更: このファイルは例示関数の実行ロジックをカプセル化しています。

    • var matchExamples = flag.String("test.example", "", "regular expression to select examples to run") という新しいグローバル変数が追加されました。これは、コマンドラインで指定された -test.example フラグの値を保持するためのものです。
    • RunExamples 関数のシグネチャが変更されました。以前は examples []InternalExample のみを受け取っていましたが、matchString func(pat, str string) (bool, error) という正規表現マッチングのための関数も受け取るようになりました。これにより、RunExamples 関数は、例示関数の名前とユーザーが指定したパターンを比較するための汎用的なメカニズムを持つことになります。
    • RunExamples 関数内に、-test.run が設定されている場合(つまり *match != "" の場合)は例示関数をスキップするロジックが追加されました。これは、go test -run <regexp> のように通常のテストをフィルタリングしている場合に、例示関数が自動的に実行されないようにするためのものです。
    • 例示関数をループ処理する中で、各例示関数の名前が *matchExamples で指定された正規表現にマッチするかどうかを matchString 関数を使ってチェックするロジックが追加されました。マッチしない例示関数はスキップされます。これにより、ユーザーは特定の例示関数のみを実行できるようになります。
  4. src/pkg/testing/testing.go の変更: このファイルには、go test コマンドのエントリポイントとなる Main 関数が含まれています。

    • Main 関数内で RunExamples 関数を呼び出す箇所が、新しいシグネチャに合わせて更新されました。具体的には、matchString 関数が RunExamples に渡されるようになりました。

これらの変更により、go test コマンドは例示関数の実行に対してより柔軟な制御を提供し、開発者のワークフローを改善します。

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

src/cmd/go/test.go

--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -99,6 +99,11 @@ directory containing the package sources, has its own flags:
 	    Run benchmarks matching the regular expression.
 	    By default, no benchmarks run.
 
+	-test.example pattern
+	    Run examples matching the regular expression.
+	    By default, all examples run, but if -test.run is set,
+	    no examples are run.
+
 	-test.cpuprofile cpu.out
 	    Write a CPU profile to the specified file before exiting.

src/cmd/go/testflag.go

--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -28,6 +28,7 @@ var usageMessage = `Usage of go test:
   -benchtime=1: passes -test.benchtime to test
   -cpu=\"\": passes -test.cpu to test
   -cpuprofile=\"\": passes -test.cpuprofile to test
+  -example=\"\": passes -test.example to test
   -memprofile=\"\": passes -test.memprofile to test
   -memprofilerate=0: passes -test.memprofilerate to test
   -parallel=0: passes -test.parallel to test
@@ -67,6 +68,7 @@ var testFlagDefn = []*testFlagSpec{\
 	{name: \"benchtime\", passToTest: true},\
 	{name: \"cpu\", passToTest: true},\
 	{name: \"cpuprofile\", passToTest: true},\
+\t{name: \"example\", passToTest: true},\
 	{name: \"memprofile\", passToTest: true},\
 	{name: \"memprofilerate\", passToTest: true},\
 	{name: \"parallel\", passToTest: true},\

src/pkg/testing/example.go

--- a/src/pkg/testing/example.go
+++ b/src/pkg/testing/example.go
@@ -6,6 +6,7 @@ package testing
 
 import (
 	\"bytes\"\n+\t\"flag\"\n \t\"fmt\"\n \t\"io\"\n \t\"os\"\n@@ -13,13 +14,18 @@ import (\
 	\"time\"\n)\n \n+var matchExamples = flag.String(\"test.example\", \"\", \"regular expression to select examples to run\")\n+\n type InternalExample struct {\n \tName   string\n \tF      func()\n \tOutput string\n }\n \n-func RunExamples(examples []InternalExample) (ok bool) {\n+func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool) {\n+\tif *match != \"\" {\n+\t\treturn // Don\'t run examples if testing is restricted: we\'re debugging.\n+\t}\n \tok = true\n \n \tvar eg InternalExample\n@@ -27,6 +33,14 @@ func RunExamples(examples []InternalExample) (ok bool) {\n \tstdout, stderr := os.Stdout, os.Stderr\n \n \tfor _, eg = range examples {\n+\t\tmatched, err := matchString(*matchExamples, eg.Name)\n+\t\tif err != nil {\n+\t\t\tfmt.Fprintf(os.Stderr, \"testing: invalid regexp for -test.example: %s\\n\", err)\n+\t\t\tos.Exit(1)\n+\t\t}\n+\t\tif !matched {\n+\t\t\tcontinue\n+\t\t}\n \t\tif *chatty {\n \t\t\tfmt.Printf(\"=== RUN: %s\\n\", eg.Name)\n \t\t}\

src/pkg/testing/testing.go

--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -280,7 +280,7 @@ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest,\
 	before()\n \tstartAlarm()\n \ttestOk := RunTests(matchString, tests)\n-\texampleOk := RunExamples(examples)\n+\texampleOk := RunExamples(matchString, examples)\n \tif !testOk || !exampleOk {\n \t\tfmt.Println(\"FAIL\")\n \t\tos.Exit(1)\n```

## コアとなるコードの解説

### `src/cmd/go/test.go` の変更点

この変更は、`go test` コマンドのヘルプメッセージに `-test.example` フラグの説明を追加するものです。ユーザーが `go help test` を実行した際に、この新しいフラグの目的と使用方法(正規表現によるフィルタリング、および `-test.run` との連携)を理解できるようにします。これは、新しい機能の発見可能性と使いやすさを向上させるための重要なドキュメント変更です。

### `src/cmd/go/testflag.go` の変更点

このファイルは、`go test` コマンドが受け付けるフラグと、それらを `testing` パッケージにどのように渡すかを定義しています。変更は、`testFlagDefn` スライスに `{name: "example", passToTest: true}` というエントリを追加することで、`-test.example` フラグが `go test` コマンドによって認識され、その値が `testing` パッケージに転送されるようにします。これにより、コマンドラインで指定された `-test.example` の値が、テスト実行ロジックに反映されるようになります。

### `src/pkg/testing/example.go` の変更点

このファイルは、例示関数の実行に関する主要なロジックを含んでいます。

1.  **`flag` パッケージのインポートと `matchExamples` 変数の追加**:
    `import "flag"` が追加され、`var matchExamples = flag.String("test.example", "", "regular expression to select examples to run")` が定義されました。これは、コマンドラインから渡される `-test.example` フラグの値を文字列として受け取り、`matchExamples` 変数に格納するためのものです。この変数は、後続の例示関数フィルタリングロジックで使用されます。

2.  **`RunExamples` 関数のシグネチャ変更**:
    `func RunExamples(examples []InternalExample) (ok bool)` から `func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool)` へと変更されました。これにより、`RunExamples` 関数は、正規表現パターンと文字列をマッチングさせるための汎用的な関数 `matchString` を引数として受け取るようになりました。これは、テストフレームワークの柔軟性を高め、正規表現マッチングのロジックを外部から注入できるようにするための設計変更です。

3.  **`-test.run` が設定されている場合の例示関数スキップロジック**:
    `if *match != "" { return }` という行が追加されました。ここで `*match` は、`go test -run <regexp>` で指定される正規表現パターンを保持する変数です。この条件が真の場合(つまり、通常のテストが正規表現でフィルタリングされている場合)、例示関数は実行されずに即座にリターンします。これは、開発者が特定のテストに集中している際に、例示関数が意図せず実行されるのを防ぐための挙動変更です。

4.  **例示関数の正規表現フィルタリングロジック**:
    例示関数をループ処理する中で、以下のコードが追加されました。
    ```go
    		matched, err := matchString(*matchExamples, eg.Name)
    		if err != nil {
    			fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.example: %s\\n", err)
    			os.Exit(1)
    		}
    		if !matched {
    			continue
    		}
    ```
    このコードは、各例示関数 (`eg`) の名前 (`eg.Name`) が、ユーザーが `-test.example` で指定した正規表現 (`*matchExamples`) にマッチするかどうかを `matchString` 関数を使ってチェックします。
    *   正規表現のパースにエラーがあった場合は、エラーメッセージを出力してプログラムを終了します。
    *   マッチしない場合は `continue` を実行し、現在の例示関数の実行をスキップして次の例示関数に進みます。
    これにより、ユーザーは `-test.example` フラグを使って、実行したい例示関数を正規表現で絞り込むことができるようになります。

### `src/pkg/testing/testing.go` の変更点

このファイルは、`go test` コマンドのメインエントリポイントである `Main` 関数を含んでいます。

1.  **`RunExamples` 関数の呼び出し更新**:
    `exampleOk := RunExamples(examples)` から `exampleOk := RunExamples(matchString, examples)` へと変更されました。これは、`src/pkg/testing/example.go` で `RunExamples` 関数のシグネチャが変更されたことに伴う修正です。`Main` 関数は、`matchString` 関数(正規表現マッチングのロジックを提供する)を `RunExamples` に渡すことで、例示関数のフィルタリング機能が正しく動作するようにします。

これらの変更は、Goのテストフレームワークにおける例示関数の管理と実行の柔軟性を大幅に向上させ、開発者がより効率的に作業できるように貢献しています。

## 関連リンク

- Go CL 5697069: [https://golang.org/cl/5697069](https://golang.org/cl/5697069)

## 参考にした情報源リンク

- GitHub Commit: [https://github.com/golang/go/commit/5876b4eb2881336c9e7007c957002d15ef54a190](https://github.com/golang/go/commit/5876b4eb2881336c9e7007c957002d15ef54a190)
- Go の testing パッケージに関する公式ドキュメント (一般的な情報): [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
- Go の Example 関数に関する公式ドキュメント (一般的な情報): [https://go.dev/blog/examples](https://go.dev/blog/examples)