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

[インデックス 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に出力されるように維持されています。

具体的な変更点は以下の通りです。

  1. ベンチマーク結果の出力:

    • src/pkg/testing/benchmark.goにおいて、ベンチマーク名と結果の出力がprint()からfmt.Printf()に変更され、これにより標準出力に書き込まれるようになりました。
    • -test.benchオプションの正規表現エラーやGOMAXPROCSの設定に関する警告は、引き続きfmt.Fprintf(os.Stderr, ...)を使用して標準エラー出力に送られます。これは、これらがテスト結果ではなく、テスト実行環境や設定に関するエラー/警告であるためです。
  2. Exampleテストの出力:

    • src/pkg/testing/example.goにおいて、Exampleテストの実行状況(=== RUN:, --- FAIL:, --- PASS:)や結果の出力がfmt.Fprintln(os.Stderr, ...)fmt.Fprintf(os.Stderr, ...)からfmt.Printf()に変更され、標準出力に書き込まれるようになりました。
    • Exampleテスト内部で発生したパニックや、パイプ処理に関する内部エラーは、引き続きos.Stderrに報告されます。これは、これらがテスト対象コードのエラーではなく、テストハーネス自体の問題であるためです。
  3. 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.gosrc/pkg/testing/example.gosrc/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パッケージが生成する出力のセマンティクスを明確にすることにあります。

  1. テスト結果の標準出力への統一:

    • RunBenchmarks関数では、ベンチマークの実行結果(ベンチマーク名と測定値)がfmt.Printfを使って標準出力に書き出されるようになりました。これにより、ベンチマーク結果を簡単にパイプで他のツールに渡したり、ファイルにリダイレクトして後で分析したりすることが可能になります。
    • RunExamples関数では、Exampleテストの実行状況(=== RUN:, --- FAIL:, --- PASS:)と、期待される出力と実際に出力された内容の比較結果がfmt.Printfを使って標準出力に書き出されます。これにより、Exampleテストの実行ログが通常のプログラム出力として扱われるようになります。
    • Main関数(テスト実行のエントリポイント)では、テスト全体のPASS/FAILステータスがfmt.Printlnを使って標準出力に書き出されます。
    • report関数(個々のテスト結果を報告)では、個々のテストのFAIL/PASSステータスと詳細がfmt.Printfを使って標準出力に書き出されます。
  2. テストフレームワークエラーの標準エラー出力への維持:

    • RunBenchmarksRunTestsparseCpuListなどの関数で発生する、-test.bench-test.runオプションの正規表現エラー、-test.cpuオプションの不正な値エラー、GOMAXPROCSの設定に関する警告などは、引き続きfmt.Fprintf(os.Stderr, ...)を使って標準エラー出力に書き出されます。これらはテスト対象のコードの問題ではなく、テストの実行環境や設定、あるいはtestingパッケージ自体の内部的な問題を示すため、標準エラー出力が適切です。
    • RunExamples関数内で発生するパニック(テスト対象コードではなく、Exampleテストの実行中に発生した予期せぬエラー)や、パイプ処理に関する内部エラーもos.Stderrに報告されます。

この分離により、Goのテスト出力はより予測可能で、スクリプト処理に適したものとなり、UnixのI/O慣習に準拠するようになりました。開発者は、テスト結果とテストフレームワークのエラーを明確に区別して扱うことができるため、CI/CDパイプラインでのテスト結果の収集や、問題発生時のデバッグが容易になります。

関連リンク

参考にした情報源リンク

  • Unixの標準ストリームに関する一般的な情報 (例: Wikipedia)
  • Go言語の公式ドキュメントとソースコード
  • Go言語のテストに関するブログ記事やチュートリアル (一般的な慣習の理解のため)