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

[インデックス 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関数も無条件に実行されていました。

この挙動は、以下のような問題を引き起こす可能性がありました。

  1. 無駄な実行: 出力検証の必要がないExample関数が実行されることで、テスト全体の実行時間が無駄に長くなる。
  2. 誤解: 出力がないにもかかわらず実行されることで、開発者がそのExample関数の意図を誤解する可能性がある。
  3. リソース消費: 特にリソースを消費するような処理を含む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で終わるファイル)をスキャンする。
  • TestBenchmarkExample関数を識別する。
  • これらの関数を実行するためのメインテストファイル(_testmain.goのような一時ファイル)を生成する。
  • 生成されたテストファイルをコンパイルし、実行する。
  • 実行結果を解析し、ユーザーに報告する。

このコミットは、このgotestの内部ロジック、特にExample関数の処理部分に修正を加えています。

技術的詳細

このコミットの技術的な核心は、gotestExample関数を検出する際に、そのドキュメントコメントの内容をチェックし、期待される出力が指定されていない場合にその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.gowriteTestmainGo関数も修正されています。この関数は、テストを実行するための_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 (コミットメッセージに記載されているリンク)