[インデックス 11235] ファイルの概要
このコミットは、Go言語のテストスイートにおける複数のテストファイルに対して行われた変更を記録しています。主な目的は、テストの出力を標準出力に直接行うのではなく、内部的に文字列として構築し、期待される結果と比較する形式に修正することです。これにより、gccgoのような並列テスト実行環境や、golden.outのような出力比較メカニズムを持たない環境でのテストの信頼性と互換性を向上させています。また、重複するテストファイルtest/ken/simpprint.goの削除も含まれています。
コミット
commit f2030938522fae7c6b65569a20a7b9ed1431b8f8
Author: Ian Lance Taylor <iant@golang.org>
Date: Wed Jan 18 14:31:31 2012 -0800
test: change several tests to not print
This will make these tests more meaningful for gccgo, which
runs tests in parallel and has no equivalent to golden.out.
Remove ken/simpprint.go since it duplicates helloworld.go.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5536058
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f2030938522fae7c6b65569a20a7b9ed1431b8f8
元コミット内容
test: change several tests to not print
This will make these tests more meaningful for gccgo, which
runs tests in parallel and has no equivalent to golden.out.
Remove ken/simpprint.go since it duplicates helloworld.go.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5536058
変更の背景
このコミットの主な背景には、Go言語のテストフレームワークと、異なるGoコンパイラ実装(特にgccgo)におけるテスト実行環境の特性があります。
- 並列テスト実行への対応: 従来のGoテストの一部は、テスト結果を標準出力(
stdout)に直接print関数で出力し、その出力をgolden.outというファイルに保存された「ゴールデン」な出力と比較することで検証していました。しかし、gccgoのようなコンパイラはテストを並列で実行する可能性があり、複数のテストが同時に標準出力に書き込むと、出力が混ざり合い、golden.outとの比較が不安定になる問題がありました。 golden.outの代替:gccgoには、Goの標準テストツールが提供するgolden.outのような出力比較メカニズムがありませんでした。そのため、テストが標準出力に依存していると、gccgo環境でのテストの信頼性が低下するか、そもそもテストが実行できないという問題が生じます。- テストの自己完結性: テストが自身の内部で結果を検証し、
panicやエラーコードで成功/失敗を示すようにすることで、外部の出力比較メカニズムに依存せず、より堅牢で移植性の高いテストになります。 - コードの重複排除:
test/ken/simpprint.goがhelloworld.goと機能的に重複していたため、コードベースの整理の一環として削除されました。
これらの理由から、テストの出力を標準出力から切り離し、テストコード内で直接結果を検証する形式に変更する必要がありました。
前提知識の解説
Go言語のprint組み込み関数とfmtパッケージ
Go言語には、printとprintlnという組み込み関数が存在します。これらは主にデバッグ用途や、非常にシンプルな出力のために提供されています。しかし、これらはfmtパッケージの関数(例: fmt.Print, fmt.Println, fmt.Sprintf)とは異なり、以下の点で制約があります。
- 型安全性と書式設定:
printは型安全ではなく、書式設定の機能も限定的です。一方、fmtパッケージはGoの型システムと統合されており、多様な書式設定オプション(%v,%d,%sなど)を提供します。 - 出力先:
printは常に標準エラー出力(stderr)に出力されることが保証されています(ただし、実装依存の部分もあります)。fmt.Printなどはデフォルトで標準出力(stdout)に出力されますが、fmt.Fprintなどを使えば任意のio.Writerに出力先を変更できます。 - 戻り値:
printは戻り値がありません。fmtパッケージの関数は、書き込まれたバイト数とエラーを返します。 - 並列実行時の問題: 複数のゴルーチンが同時に
printを呼び出すと、出力がインターリーブ(混ざり合う)する可能性があり、特にテストの出力比較においては問題となります。
このコミットでは、print関数による直接的な標準出力への書き込みを避け、fmt.Sprintlnや文字列結合を用いて期待される出力をメモリ上の文字列として構築し、その文字列を直接比較することで、並列実行時の出力の競合を防ぎ、テストの自己完結性を高めています。
gccgoとGoコンパイラ
Go言語には複数のコンパイラ実装が存在します。
- gc (Go Compiler): Goプロジェクトの公式コンパイラであり、Go言語のソースコードをネイティブバイナリにコンパイルします。ほとんどのGo開発者が日常的に使用しているものです。
- gccgo: GCC (GNU Compiler Collection) のフロントエンドとして実装されたGoコンパイラです。GCCの最適化パスやバックエンドを利用できるため、特定の環境や既存のGCCツールチェーンとの統合が必要な場合に利用されます。
gccgoはgcとは異なるテスト実行環境や並列処理の特性を持つことがあります。
このコミットは、gccgoのような代替コンパイラ環境でのテストの互換性と信頼性を向上させることを目的としています。
golden.outファイル
Goのテストフレームワークにおいて、golden.outはテストの期待される出力(「ゴールデン」な出力)を記録するファイルとして使われることがあります。テスト実行時にプログラムの出力をこのgolden.outと比較し、一致すればテスト成功、不一致であればテスト失敗と判断します。これは、特にテキストベースの出力を持つテストにおいて、出力内容の正確性を検証する一般的な手法です。しかし、この手法は出力が常に予測可能で、並列実行によって乱されない場合にのみ有効です。
技術的詳細
このコミットで行われた技術的な変更は、主に以下のパターンに従っています。
print関数の置き換え: 既存のテストコードでprint関数が直接標準出力に文字列や変数の値を出力している箇所を特定します。- 文字列バッファの導入: テスト関数のスコープ内で、
var r stringのような形で文字列変数を宣言し、これを「出力バッファ」として使用します。 fmt.Sprintlnまたは文字列結合による出力のキャプチャ:printの代わりに、fmt.Sprintln(またはfmt.Sprintf)を使用して、出力したい内容を文字列としてフォーマットし、その結果を先ほど導入した文字列バッファrに+=演算子で追記していきます。これにより、実際の出力はメモリ上の文字列rに蓄積されます。- 例:
print("hello\\n")はr += "hello\\n"に。 - 例:
print(i, " ", x.val, "\\n")はr += fmt.Sprintln(i, x.val)に。
- 例:
- 期待値との比較: テストの最後に、構築された文字列バッファ
rが、事前に定義された期待される文字列expectと完全に一致するかどうかを比較します。 - テスト失敗時の
panic: 比較の結果、r != expectであれば、panic(r)(またはpanic(0)など)を呼び出してテストを失敗させます。これにより、テストフレームワークはテストの失敗を検知できます。 golden.outからの関連エントリの削除: 変更されたテストがもはや標準出力に依存しないため、test/golden.outファイルからこれらのテストに関連する期待出力のエントリが削除されています。- 重複ファイルの削除:
test/ken/simpprint.goがhelloworld.goと重複していたため、削除されました。これはコードベースのクリーンアップと保守性の向上に貢献します。
このアプローチにより、テストは外部の出力比較メカニズムに依存せず、自己完結的に結果を検証できるようになり、並列実行環境での信頼性が向上します。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
test/fixedbugs/bug027.go:printをfmt.Sprintlnと文字列結合に置き換え、期待値との比較を追加。test/fixedbugs/bug070.go:printをfmt.Sprintlnと文字列結合に置き換え、期待値との比較を追加。test/golden.out: 複数のテスト(peano.go,turing.go,cplx4.go,label.go,rob1.go,rob2.go,simpprint.go,simpswitch.go,bug027.go,bug070.go)に関連する期待出力が削除。test/ken/cplx4.go:fmt.Printfをfmt.Sprintfとwant関数(内部で文字列比較を行うヘルパー関数)に置き換え。test/ken/label.go:print呼び出しを削除。test/ken/rob1.go:ItemインターフェースのPrintメソッドの戻り値をstringに変更し、printを文字列結合に置き換え、期待値との比較を追加。test/ken/rob2.go:SlistのPrintOneおよびPrintメソッドの戻り値をstringに変更し、printを文字列結合に置き換え、期待値との比較を追加。test/ken/simpprint.go: ファイル自体が削除。test/ken/simpswitch.go:printを文字列結合に置き換え、期待値との比較を追加。test/peano.go:printを削除し、計算結果を直接results配列の期待値と比較するロジックを追加。test/turing.go:printを文字列結合に置き換え、期待値との比較を追加。
コアとなるコードの解説
test/fixedbugs/bug027.goの変更例
--- a/test/fixedbugs/bug027.go
+++ b/test/fixedbugs/bug027.go
@@ -6,6 +6,8 @@
package main
+import "fmt"
+
type Element interface {
}
@@ -43,7 +45,7 @@ func main() {
i4 := new(I)
i4.val = 44444
v := New()
- print("hi\\n")
+ r := "hi\\n"
v.Insert(i4)
v.Insert(i3)
v.Insert(i2)
@@ -52,10 +54,25 @@ func main() {
for i := 0; i < v.nelem; i++ {
var x *I
x = v.At(i).(*I)
- print(i, " ", x.val, "\\n") // prints correct list
+ r += fmt.Sprintln(i, x.val) // prints correct list
}
for i := 0; i < v.nelem; i++ {
- print(i, " ", v.At(i).(*I).val, "\\n")
+ r += fmt.Sprintln(i, v.At(i).(*I).val)
+ }
+ expect := `hi
+0 44444
+1 3333
+2 222
+3 11
+4 0
+0 44444
+1 3333
+2 222
+3 11
+4 0
+`
+ if r != expect {
+ panic(r)
}
}
この変更では、まずfmtパッケージをインポートしています。次に、print("hi\\n")のような直接的な出力の代わりに、r := "hi\\n"という文字列変数rを導入し、そこに初期値を代入しています。ループ内のprint呼び出しもr += fmt.Sprintln(i, x.val)のように変更され、fmt.Sprintlnでフォーマットされた文字列がrに追記されます。最後に、構築された文字列rが、複数行文字列リテラルで定義されたexpect変数と一致するかを検証し、一致しない場合はpanic(r)でテストを失敗させています。
test/ken/rob1.goの変更例
--- a/test/ken/rob1.go
+++ b/test/ken/rob1.go
@@ -7,7 +7,7 @@
package main
type Item interface {
- Print()
+ Print() string
}
type ListItem struct {
@@ -30,12 +30,14 @@ func (list *List) Insert(i Item) {
list.head = item
}
-func (list *List) Print() {
+func (list *List) Print() string {
+\tr := ""
i := list.head
for i != nil {
- i.item.Print()
+ r += i.item.Print()
i = i.next
}
+\treturn r
}
// Something to put in a list
@@ -48,8 +50,8 @@ func (this *Integer) Init(i int) *Integer {
return this
}
-func (this *Integer) Print() {
- print(this.val)
+func (this *Integer) Print() string {
+ return string(this.val + '0')
}
func main() {\
@@ -61,6 +63,8 @@ func main() {\
list.Insert(integer)
}
- list.Print()
- print("\\n")
+\tr := list.Print()
+\tif r != "9876543210" {
+\t\tpanic(r)
+\t}
}
この例では、ItemインターフェースのPrint()メソッドがPrint() stringに変更され、文字列を返すようになりました。List構造体のPrint()メソッドも同様に文字列を返すように変更され、内部で各ItemのPrint()結果をrに結合しています。Integer構造体のPrint()メソッドも、print(this.val)からreturn string(this.val + '0')に変更され、文字列表現を返すようになっています。main関数では、list.Print()の戻り値を変数rに格納し、期待される文字列"9876543210"と比較しています。
これらの変更は、Goのテストがより堅牢で、異なるコンパイラや実行環境においても一貫した結果を提供できるようにするための重要なステップです。
関連リンク
- Go言語の
fmtパッケージ: https://pkg.go.dev/fmt - Go言語のテスト: https://go.dev/doc/code#Testing
- GCCGo: https://gcc.gnu.org/onlinedocs/gccgo/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコードリポジトリ(特に
testディレクトリ) - Go言語のIssueトラッカーやメーリングリスト(
golang.org/cl/5536058など) - GCCGoのドキュメント