[インデックス 10406] ファイルの概要
このドキュメントは、Go言語のtesting
パッケージにおけるコミットa3fb1aec6bdb680a86758cc330a8c5305f6dd902
について、その変更内容、背景、技術的詳細を包括的に解説します。このコミットは、テスト結果の出力先を標準出力(stdout)に統一し、テストフレームワーク自体のエラーは標準エラー出力(stderr)に分離するという重要な変更を導入しています。
コミット
commit a3fb1aec6bdb680a86758cc330a86758cc330a8c5305f6dd902
Author: Russ Cox <rsc@golang.org>
Date: Tue Nov 15 13:09:19 2011 -0500
testing: print test results to standard output
Errors in the code under test go to standard output.
Errors in testing or its usage go to standard error.
R=r
CC=golang-dev
https://golang.org/cl/5374090
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a3fb1aec6bdb680a86758cc330a8c5305f6dd902
元コミット内容
このコミットの元のメッセージは以下の通りです。
testing: print test results to standard output
Errors in the code under test go to standard output.
Errors in testing or its usage go to standard error.
これは、Goのテストフレームワークがテスト結果をどのように報告するかに関する基本的な方針転換を示しています。
変更の背景
Go言語のtesting
パッケージは、Goプログラムのテストを記述・実行するための標準的な方法を提供します。このコミット以前は、テストの成功/失敗のメッセージやベンチマークの結果など、一部のテスト出力が標準エラー出力(os.Stderr
)に書き込まれていました。しかし、これは一般的なUnix/Linuxのプログラムにおける標準出力と標準エラー出力の慣習とは異なっていました。
一般的な慣習では、プログラムの通常の出力(期待される結果、情報メッセージなど)は標準出力(stdout)に送られ、エラーメッセージや診断情報などの異常な出力は標準エラー出力(stderr)に送られます。テスト結果は、テストが成功したか失敗したかを示す「通常の」出力と見なされるべきであり、エラーとは区別されるべきです。
このコミットは、この慣習に合わせることを目的としています。これにより、テスト結果を他のプログラムの出力と容易に区別できるようになり、スクリプトでのテスト結果のパースやリダイレクトがより直感的になります。例えば、テスト結果をファイルにリダイレクトしつつ、テストフレームワーク自体のエラーはコンソールに表示するといった運用が可能になります。
前提知識の解説
標準出力 (stdout) と標準エラー出力 (stderr)
Unix系オペレーティングシステムでは、プロセスはデフォルトで3つの標準I/Oストリームを持っています。
- 標準入力 (stdin): プロセスへの入力データが供給されるストリーム。通常はキーボードに接続されています。
- 標準出力 (stdout): プロセスが通常の出力データを書き込むストリーム。通常はディスプレイに接続されています。ファイルにリダイレクトされることがよくあります。
- 標準エラー出力 (stderr): プロセスがエラーメッセージや診断情報を書き込むストリーム。通常はディスプレイに接続されています。標準出力とは独立してリダイレクトできます。
これらのストリームは、シェルを通じてリダイレクトやパイプ処理を行うことで、柔軟なI/O操作を可能にします。例えば、command > output.txt
は標準出力をoutput.txt
にリダイレクトし、command 2> error.log
は標準エラー出力をerror.log
にリダイレクトします。
Go言語におけるI/O
Go言語では、os
パッケージが標準I/Oストリームへのアクセスを提供します。
os.Stdout
: 標準出力に書き込むための*os.File
型変数。os.Stderr
: 標準エラー出力に書き込むための*os.File
型変数。
また、fmt
パッケージはこれらのストリームへのフォーマットされた出力機能を提供します。
fmt.Print()
,fmt.Println()
,fmt.Printf()
: デフォルトでos.Stdout
に書き込みます。fmt.Fprint()
,fmt.Fprintln()
,fmt.Fprintf()
: 第一引数にio.Writer
インターフェースを満たすオブジェクト(例:os.Stderr
)を指定することで、任意の出力先に書き込めます。
println()
はGoの組み込み関数ですが、これはfmt.Println(os.Stdout, ...)
とほぼ同等であり、通常はデバッグ用途に限定されます。
技術的詳細
このコミットの主要な技術的変更は、testing
パッケージ内の出力ロジックをos.Stderr
からos.Stdout
へ、またはprintln
からfmt.Printf
へ変更することです。同時に、テストフレームワーク自体のエラー(例えば、正規表現のパースエラーや内部的なパイプ処理のエラーなど)は引き続きos.Stderr
に出力されるように維持されています。
具体的な変更点は以下の通りです。
-
ベンチマーク結果の出力:
src/pkg/testing/benchmark.go
において、ベンチマーク名と結果の出力がprint()
からfmt.Printf()
に変更され、これにより標準出力に書き込まれるようになりました。-test.bench
オプションの正規表現エラーやGOMAXPROCS
の設定に関する警告は、引き続きfmt.Fprintf(os.Stderr, ...)
を使用して標準エラー出力に送られます。これは、これらがテスト結果ではなく、テスト実行環境や設定に関するエラー/警告であるためです。
-
Exampleテストの出力:
src/pkg/testing/example.go
において、Exampleテストの実行状況(=== RUN:
,--- FAIL:
,--- PASS:
)や結果の出力がfmt.Fprintln(os.Stderr, ...)
やfmt.Fprintf(os.Stderr, ...)
からfmt.Printf()
に変更され、標準出力に書き込まれるようになりました。- Exampleテスト内部で発生したパニックや、パイプ処理に関する内部エラーは、引き続き
os.Stderr
に報告されます。これは、これらがテスト対象コードのエラーではなく、テストハーネス自体の問題であるためです。
-
Test結果の出力:
src/pkg/testing/testing.go
において、テスト全体の成功/失敗を示すPASS
/FAIL
メッセージがfmt.Fprintln(os.Stderr, ...)
からfmt.Println()
に変更され、標準出力に書き込まれるようになりました。- 個々のテストのレポート(
--- FAIL:
,--- PASS:
)もfmt.Fprintf(os.Stderr, ...)
からfmt.Printf()
に変更され、標準出力に送られます。 -test.run
オプションの正規表現エラーや-test.cpu
オプションの不正な値に関するエラーは、引き続きfmt.Fprintf(os.Stderr, ...)
を使用して標準エラー出力に送られます。これらはテストの実行設定に関するエラーです。=== RUN
メッセージもprintln()
からfmt.Printf()
に変更され、標準出力に送られます。
この変更により、Goのテスト実行時の出力は、通常のテスト結果(PASS/FAIL、ベンチマーク結果、Example出力)が標準出力に、テストフレームワーク自体のエラーや警告が標準エラー出力に、という明確な分離が実現されました。
コアとなるコードの変更箇所
このコミットは、主にsrc/pkg/testing/benchmark.go
、src/pkg/testing/example.go
、src/pkg/testing/testing.go
の3つのファイルに影響を与えています。
変更のパターンは以下の通りです。
println(...)
->fmt.Printf(...)
またはfmt.Println(...)
fmt.Fprint(os.Stderr, ...)
->fmt.Print(...)
またはfmt.Printf(...)
fmt.Fprintln(os.Stderr, ...)
->fmt.Println(...)
fmt.Fprintf(os.Stderr, ...)
->fmt.Printf(...)
ただし、テストフレームワーク自体のエラーメッセージに関しては、引き続きos.Stderr
への出力が維持されています。
例1: src/pkg/testing/benchmark.go
の変更
--- a/src/pkg/testing/benchmark.go
+++ b/src/pkg/testing/benchmark.go
@@ -205,7 +205,7 @@ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks [\
for _, Benchmark := range benchmarks {\
matched, err := matchString(*matchBenchmarks, Benchmark.Name)\
if err != nil {\
- println("invalid regexp for -test.bench:", err.Error())
+ fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
os.Exit(1)
}
if !matched {
@@ -218,11 +218,11 @@ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks [\
if procs != 1 {\
benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs)
}
- print(fmt.Sprintf("%s\t", benchName))
+ fmt.Printf("%s\t", benchName)
r := b.run()
- print(fmt.Sprintf("%v\n", r))
+ fmt.Printf("%v\n", r)
if p := runtime.GOMAXPROCS(-1); p != procs {\
- print(fmt.Sprintf("%s left GOMAXPROCS set to %d\n", benchName, p))
+ fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
}
}
}
例2: src/pkg/testing/example.go
の変更
--- a/src/pkg/testing/example.go
+++ b/src/pkg/testing/example.go
@@ -67,16 +66,15 @@ func RunExamples(examples []InternalExample) (ok bool) {\
\tout := <-outC
\t// report any errors
+\t\ttstr := fmt.Sprintf("(%.2f seconds)", float64(ns)/1e9)
\tif out != eg.Output {\
-\t\t\tfmt.Fprintf(\
-\t\t\t\tos.Stderr,\
-\t\t\t\t"--- FAIL: %s\ngot:\n%s\nwant:\n%s\n",\
-\t\t\t\teg.Name, out, eg.Output,\
+\t\t\tfmt.Printf(\
+\t\t\t\t"--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n",\
+\t\t\t\teg.Name, tstr, out, eg.Output,\
\t\t)\
\t\tok = false
\t} else if *chatty {\
-\t\t\ttstr := fmt.Sprintf("(%.2f seconds)", float64(ns)/1e9)
-\t\t\tfmt.Fprintln(os.Stderr, "--- PASS:", eg.Name, tstr)
+\t\t\tfmt.Printf("--- PASS: %s %s\n", eg.Name, tstr)
\t}\
}\
コアとなるコードの解説
このコミットの核心は、Goのtesting
パッケージが生成する出力のセマンティクスを明確にすることにあります。
-
テスト結果の標準出力への統一:
RunBenchmarks
関数では、ベンチマークの実行結果(ベンチマーク名と測定値)がfmt.Printf
を使って標準出力に書き出されるようになりました。これにより、ベンチマーク結果を簡単にパイプで他のツールに渡したり、ファイルにリダイレクトして後で分析したりすることが可能になります。RunExamples
関数では、Exampleテストの実行状況(=== RUN:
,--- FAIL:
,--- PASS:
)と、期待される出力と実際に出力された内容の比較結果がfmt.Printf
を使って標準出力に書き出されます。これにより、Exampleテストの実行ログが通常のプログラム出力として扱われるようになります。Main
関数(テスト実行のエントリポイント)では、テスト全体のPASS
/FAIL
ステータスがfmt.Println
を使って標準出力に書き出されます。report
関数(個々のテスト結果を報告)では、個々のテストのFAIL
/PASS
ステータスと詳細がfmt.Printf
を使って標準出力に書き出されます。
-
テストフレームワークエラーの標準エラー出力への維持:
RunBenchmarks
、RunTests
、parseCpuList
などの関数で発生する、-test.bench
や-test.run
オプションの正規表現エラー、-test.cpu
オプションの不正な値エラー、GOMAXPROCS
の設定に関する警告などは、引き続きfmt.Fprintf(os.Stderr, ...)
を使って標準エラー出力に書き出されます。これらはテスト対象のコードの問題ではなく、テストの実行環境や設定、あるいはtesting
パッケージ自体の内部的な問題を示すため、標準エラー出力が適切です。RunExamples
関数内で発生するパニック(テスト対象コードではなく、Exampleテストの実行中に発生した予期せぬエラー)や、パイプ処理に関する内部エラーもos.Stderr
に報告されます。
この分離により、Goのテスト出力はより予測可能で、スクリプト処理に適したものとなり、UnixのI/O慣習に準拠するようになりました。開発者は、テスト結果とテストフレームワークのエラーを明確に区別して扱うことができるため、CI/CDパイプラインでのテスト結果の収集や、問題発生時のデバッグが容易になります。
関連リンク
- Go
testing
パッケージのドキュメント: https://pkg.go.dev/testing - Go
fmt
パッケージのドキュメント: https://pkg.go.dev/fmt - Go
os
パッケージのドキュメント: https://pkg.go.dev/os - このコミットのGo Gerrit Code Reviewリンク: https://golang.org/cl/5374090
参考にした情報源リンク
- Unixの標準ストリームに関する一般的な情報 (例: Wikipedia)
- Go言語の公式ドキュメントとソースコード
- Go言語のテストに関するブログ記事やチュートリアル (一般的な慣習の理解のため)