[インデックス 10281] ファイルの概要
このコミットは、Go言語のテストツールであるgotest
の挙動に関する改善です。具体的には、期待される出力を持たない(つまり、ドキュメントコメントが空の)Example
関数がテスト実行時にスキップされるように変更されました。これにより、無駄なテストの実行を避け、テストプロセスの効率化と明確化が図られています。
コミット
- コミットハッシュ:
2fcb045242bfdf96fdc3dbfc847847ed14ebebc1
- 作者: Andrew Gerrand adg@golang.org
- コミット日時: 2011年11月8日 火曜日 10:11:07 +1100
- コミットメッセージ:
gotest: don't run examples that have no expected output R=golang-dev, rsc CC=golang-dev https://golang.org/cl/5364041
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2fcb045242bfdf96fdc3dbfc847847ed14ebebc1
元コミット内容
このコミットの目的は、gotest
コマンドがGoのExample
関数を実行する際に、期待される出力が指定されていない(ドキュメントコメントが空の)Example
関数をスキップすることです。これにより、テストの実行がより効率的になり、意図しないテストの実行を防ぎます。
変更の背景
Go言語のtesting
パッケージには、コードの利用例を示すためのExample
関数という機能があります。これらの関数は、通常、そのドキュメントコメントに期待される出力(標準出力または標準エラー出力)を記述し、go test
コマンドがその出力と実際の実行結果を比較することで、例が正しく動作するかどうかを検証します。
しかし、ドキュメントコメントに期待される出力が記述されていないExample
関数も存在し得ます。このような関数は、本来であればテストとして機能しないか、あるいは単にコードの例を示すだけで、出力の検証を必要としない場合があります。以前のgotest
の挙動では、このような出力を持たないExample
関数も無条件に実行されていました。
この挙動は、以下のような問題を引き起こす可能性がありました。
- 無駄な実行: 出力検証の必要がない
Example
関数が実行されることで、テスト全体の実行時間が無駄に長くなる。 - 誤解: 出力がないにもかかわらず実行されることで、開発者がその
Example
関数の意図を誤解する可能性がある。 - リソース消費: 特にリソースを消費するような処理を含む
Example
関数が、不必要に実行されることでシステムリソースを圧迫する。
このコミットは、これらの問題を解決するために、期待される出力を持たないExample
関数をgotest
が自動的にスキップするように変更しました。これにより、gotest
の効率性と正確性が向上します。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とツールに関する知識が必要です。
1. Go言語のtesting
パッケージ
Go言語には、ユニットテスト、ベンチマークテスト、そしてExample
関数をサポートするための標準ライブラリtesting
パッケージが用意されています。
- テスト関数:
func TestXxx(*testing.T)
というシグネチャを持つ関数で、ユニットテストを記述します。 - ベンチマーク関数:
func BenchmarkXxx(*testing.B)
というシグネチャを持つ関数で、コードのパフォーマンスを測定します。 - Example関数:
func ExampleXxx()
というシグネチャを持つ関数で、コードの利用例を示します。これらの関数は、そのドキュメントコメントに期待される出力を記述することで、その出力が実際の実行結果と一致するかどうかをgo test
が検証します。
2. Example
関数とその出力検証
Example
関数は、Goのドキュメント生成ツール(godoc
)によってドキュメントに組み込まれるだけでなく、go test
コマンドによって実行され、その出力が検証されます。
例えば、以下のようなExample
関数があるとします。
package mypackage
import "fmt"
// ExampleHello demonstrates how to use the Hello function.
// Output:
// Hello, World!
func ExampleHello() {
fmt.Println("Hello, World!")
}
このExampleHello
関数は、fmt.Println("Hello, World!")
を実行し、その出力がドキュメントコメント内のOutput:
セクションに記述された「Hello, World!」と一致するかどうかをgo test
が確認します。もし一致しない場合、テストは失敗します。
この機能は、コードの利用例が常に最新かつ正確であることを保証する上で非常に強力です。しかし、もしOutput:
セクションが空であるか、あるいはドキュメントコメント自体が存在しない場合、そのExample
関数は出力検証の対象とはなりません。
3. gotest
コマンド(src/cmd/gotest
)
gotest
は、Goのソースコードツリー内にある内部コマンドであり、go test
コマンドのバックエンドとして機能します。go test
がユーザーからテスト実行の要求を受け取ると、内部的にgotest
を呼び出し、テストの発見、コンパイル、実行、結果の報告を行います。
gotest
の主な役割は以下の通りです。
- 指定されたパッケージ内のテストファイル(
_test.go
で終わるファイル)をスキャンする。 Test
、Benchmark
、Example
関数を識別する。- これらの関数を実行するためのメインテストファイル(
_testmain.go
のような一時ファイル)を生成する。 - 生成されたテストファイルをコンパイルし、実行する。
- 実行結果を解析し、ユーザーに報告する。
このコミットは、このgotest
の内部ロジック、特にExample
関数の処理部分に修正を加えています。
技術的詳細
このコミットの技術的な核心は、gotest
がExample
関数を検出する際に、そのドキュメントコメントの内容をチェックし、期待される出力が指定されていない場合にそのExample
関数をテスト実行の対象から除外する点にあります。
具体的には、src/cmd/gotest/gotest.go
内のgetTestNames
関数が変更されています。この関数は、Goのソースファイルからテスト、ベンチマーク、およびExample
関数を識別し、それぞれのリストに格納する役割を担っています。
変更前は、Example
関数が見つかると、そのドキュメントコメントの内容(doc.CommentText(n.Doc)
)をそのままexample
構造体のoutput
フィールドに格納し、無条件にf.examples
リストに追加していました。
変更後は、doc.CommentText(n.Doc)
で取得したドキュメントコメントのテキストをoutput
変数に一時的に格納し、そのoutput
が空文字列であるかどうかをチェックします。もし空文字列であれば、そのExample
関数は期待される出力を持たないと判断され、continue
文によってループの次のイテレーションに進みます。これにより、当該Example
関数はf.examples
リストに追加されず、結果としてテスト実行の対象から外れます。
また、src/cmd/gotest/gotest.go
のwriteTestmainGo
関数も修正されています。この関数は、テストを実行するための_testmain.go
ファイルを生成する際に、テスト、ベンチマーク、およびExample
関数のいずれも含まれていないファイルは処理をスキップするロジックを持っています。この条件にlen(f.examples) == 0
が追加されました。これにより、もしあるファイルが、今回の変更によってスキップされるようになったExample
関数のみを含んでいた場合、そのファイル全体がテストメインファイルの生成対象から除外され、さらに効率が向上します。
さらに、src/cmd/gotest/doc.go
には、この新しい挙動を反映したドキュメントの追記が行われました。「Example functions without doc comments are compiled but not executed.」(ドキュメントコメントのないExample関数はコンパイルされるが実行されない)という一文が追加され、ユーザーがこの変更を理解できるようになっています。
コアとなるコードの変更箇所
src/cmd/gotest/doc.go
--- a/src/cmd/gotest/doc.go
+++ b/src/cmd/gotest/doc.go
@@ -37,6 +37,7 @@ os.Stdout and os.Stderr is compared against their doc comment.
Multiple example functions may be provided for a given name XXX if they are
discriminated by a distinct suffix starting with "_", such as ExampleXXX_2.
+Example functions without doc comments are compiled but not executed.
See the documentation of the testing package for more information.
src/cmd/gotest/gotest.go
--- a/src/cmd/gotest/gotest.go
+++ b/src/cmd/gotest/gotest.go
@@ -231,9 +231,14 @@ func getTestNames() {
} else if isTest(name, "Benchmark") {
f.benchmarks = append(f.benchmarks, name)
} else if isTest(name, "Example") {
+ output := doc.CommentText(n.Doc)
+ if output == "" {
+ // Don't run examples with no output.
+ continue
+ }
f.examples = append(f.examples, example{
name: name,
- output: doc.CommentText(n.Doc),
+ output: output,
})
}
// TODO: worth checking the signature? Probably not.
@@ -372,7 +377,7 @@ func writeTestmainGo() {\n insideTests := false\n for _, f := range files {\n //println(f.name, f.pkg)\n- if len(f.tests) == 0 && len(f.benchmarks) == 0 {\n+ if len(f.tests) == 0 && len(f.benchmarks) == 0 && len(f.examples) == 0 {\n \t\t\tcontinue\n \t\t}\n \t\tif isOutsideTest(f.pkg) {\n```
## コアとなるコードの解説
### `src/cmd/gotest/doc.go` の変更
* `+Example functions without doc comments are compiled but not executed.`
* この行は、`gotest`のドキュメントに新しい挙動を明記しています。ドキュメントコメント(特に`Output:`セクション)を持たない`Example`関数は、コンパイルはされるものの、テスト実行時にはスキップされることを示しています。これは、ユーザーがこの変更を理解し、`Example`関数の記述方法を適切に調整できるようにするための重要な情報です。
### `src/cmd/gotest/gotest.go` の変更
#### `getTestNames` 関数内
```go
} else if isTest(name, "Example") {
+ output := doc.CommentText(n.Doc)
+ if output == "" {
+ // Don't run examples with no output.
+ continue
+ }
f.examples = append(f.examples, example{
name: name,
- output: doc.CommentText(n.Doc),
+ output: output,
})
}
output := doc.CommentText(n.Doc)
:n.Doc
は、現在処理しているExample
関数のASTノードに関連付けられたドキュメントコメントを表します。doc.CommentText
関数は、このドキュメントコメントから整形されたテキストを抽出します。このテキストには、Output:
セクションの内容も含まれます。
if output == "" { ... continue }
:- 抽出された
output
が空文字列であるかどうかをチェックします。これは、Example
関数にドキュメントコメントがないか、あるいはドキュメントコメントがあってもOutput:
セクションが空である(つまり、期待される出力が指定されていない)ことを意味します。 continue
:output
が空の場合、現在のExample
関数をf.examples
リストに追加する処理をスキップし、for
ループの次のイテレーションに進みます。これにより、期待される出力を持たないExample
関数はテスト実行の対象から除外されます。
- 抽出された
output: output,
example
構造体のoutput
フィールドに、抽出したドキュメントコメントテキスト(または空文字列)を格納します。この値は、後でgo test
が実際の出力と比較するために使用されます。変更前はdoc.CommentText(n.Doc)
が直接使われていましたが、output
変数に格納することで、条件分岐の後に再利用できるようになっています。
writeTestmainGo
関数内
for _, f := range files {
//println(f.name, f.pkg)
- if len(f.tests) == 0 && len(f.benchmarks) == 0 {
+ if len(f.tests) == 0 && len(f.benchmarks) == 0 && len(f.examples) == 0 {
continue
}
if len(f.tests) == 0 && len(f.benchmarks) == 0 && len(f.examples) == 0 { ... continue }
:- この条件は、特定のファイル(
f
)がテスト関数、ベンチマーク関数、そしてExample
関数のいずれも含まない場合に、そのファイルの処理をスキップするためのものです。 - 変更前は
len(f.examples) == 0
が含まれていませんでした。今回の変更により、期待される出力を持たないExample
関数がf.examples
リストから除外されるようになったため、もしファイルがそのようなExample
関数のみを含んでいた場合、この新しい条件によってファイル全体がテストメインファイルの生成対象から除外されるようになります。これにより、テスト実行の準備段階での無駄な処理がさらに削減されます。
- この条件は、特定のファイル(
これらの変更により、gotest
はより賢くExample
関数を扱い、期待される出力を持たないものは実行しないことで、テストプロセスの効率性と正確性を向上させています。
関連リンク
- Go言語の
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go言語の
Example
関数に関する公式ブログ記事(もしあれば、より詳細な情報が得られる可能性がありますが、このコミット時点では特定の記事は見つかりませんでした。一般的なGoのテストに関する記事を参照してください。)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/cmd/gotest
ディレクトリ) - Go言語の
doc
パッケージのドキュメント: https://pkg.go.dev/go/doc (特にCommentText
関数について) - GitHub上のGoリポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/5364041 (コミットメッセージに記載されているリンク)