[インデックス 1193] ファイルの概要
このコミットは、Go言語のテストフレームワークにおける重要な変更を示しています。具体的には、テスト関数に *testing.T 型の引数を導入し、テストの失敗やログ出力などの操作をこの T 型のメソッドを通じて行うように変更しています。これにより、テストの構造がより統一され、柔軟性が向上しました。
コミット
commit 6d30efc77215d97d57330ab2daaac338db388a17
Author: Rob Pike <r@golang.org>
Date: Wed Nov 19 14:38:05 2008 -0800
add a type testing.T and use it in tests.
update uses of gotest.
minor tweak to testing structure for protobuf.
R=rsc
DELTA=276 (71 added, 75 deleted, 130 changed)
OCL=19614
CL=19621
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6d30efc77215d97d57330ab2daaac338db388a17
元コミット内容
このコミットは、Go言語のテストフレームワークに testing.T 型を導入し、既存のテストコードを新しい testing.T を使用する形式に更新するものです。また、gotest の使用法も更新され、protobuf関連のテスト構造に微調整が加えられています。
変更の背景
Go言語の初期段階では、テストの記述方法が現在とは異なっていました。このコミットが行われた2008年当時、Goのテストはまだ進化の途上にあり、テストの実行結果の報告やエラー処理の方法が統一されていませんでした。以前のテスト関数は、ブール値を返すことでテストの成否を示したり、fmt.printf を直接使用してエラーメッセージを出力したりしていました。
このようなアプローチでは、テストの失敗を適切に報告したり、詳細なログ情報を収集したりすることが困難でした。また、テストの並列実行やセットアップ/ティアダウン処理といった高度なテスト機能を実現するためには、より構造化されたテストコンテキストが必要でした。
このコミットは、これらの課題を解決するために、テスト実行中にテストの状態を管理し、エラー報告やログ記録のための統一されたインターフェースを提供する testing.T 型を導入しました。これにより、Goのテストフレームワークはより堅牢で拡張性の高いものへと進化する基盤が築かれました。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念とテストに関する初期の状況を理解しておく必要があります。
- Go言語のパッケージとインポート: Goのコードはパッケージにまとめられ、他のパッケージの機能を利用するには
importキーワードを使用します。このコミットでは、testingパッケージが導入され、各テストファイルでインポートされるようになります。 - 関数とメソッド: Goでは、関数は独立して定義されますが、メソッドは特定の型に関連付けられ、その型のインスタンスに対して呼び出されます。
testing.T型に定義されたメソッドは、テストのコンテキスト内で呼び出されます。 - エラーハンドリング: Goでは、エラーは通常、関数の戻り値として
error型で返されます。このコミット以前のテストでは、ブール値で成否を判断したり、os.EINVALのような特定のエラー値を直接比較したりしていました。 gotest: Go言語の初期のテスト実行ツールです。このコミットでは、gotestの使用法がtesting.Tの導入に合わせて更新されています。fmtパッケージ: Goの標準ライブラリの一部で、フォーマットされたI/O(入出力)を提供します。以前のテストでは、fmt.printfを使用してテスト結果やエラーメッセージをコンソールに出力していました。panicとrecover: Goのエラー処理メカニズムの一つで、プログラムの異常終了を引き起こすpanicと、それを捕捉して回復するrecoverがあります。testing.TのFailNowやFatalメソッドは、内部的にpanicを利用してテストの即時終了を実現しています。goexit():runtimeパッケージ(Go 1.0以前はsysパッケージ)の関数で、現在のゴルーチンを終了させますが、プログラム全体は終了させません。FailNowメソッドでテストゴルーチンを終了させるために使用されます。
技術的詳細
このコミットの主要な技術的変更点は、src/lib/testing.go に testing.T 型が導入され、そのメソッドを通じてテストの実行と結果報告が抽象化されたことです。
testing.T 型の導入
testing.T は、テストの実行中にテストの状態を管理するための構造体です。
export type T struct {
errors string;
failed bool;
ch *chan *T;
}
errors: テスト中に記録されたエラーメッセージを保持する文字列。failed: テストが失敗したかどうかを示すブール値。ch: テストゴルーチンとメインのテストランナーとの間でTインスタンスをやり取りするためのチャネル。FailNowメソッドでテストを即座に終了させる際に使用されます。
testing.T のメソッド
testing.T には、テストの成否を報告したり、ログを出力したりするための様々なメソッドが追加されました。
Fail(): 現在のテストを失敗としてマークしますが、テストの実行は継続します。FailNow(): 現在のテストを失敗としてマークし、テストの実行を即座に停止します。これは、テストがこれ以上続行できない致命的なエラーに遭遇した場合に使用されます。内部的にはpanicとsys.goexit()を利用して、現在のテストゴルーチンを終了させます。Log(args ...): テストの実行中に情報をログに出力します。これはテストのデバッグに役立ちます。Logf(format string, args ...): フォーマット文字列を使用して情報をログに出力します。Error(args ...):Logを呼び出した後、Failを呼び出します。テストを失敗としてマークし、メッセージをログに出力しますが、テストの実行は継続します。Errorf(format string, args ...):Logfを呼び出した後、Failを呼び出します。Fatal(args ...):Logを呼び出した後、FailNowを呼び出します。テストを失敗としてマークし、メッセージをログに出力し、テストの実行を即座に停止します。Fatalf(format string, args ...):Logfを呼び出した後、FailNowを呼び出します。
テスト関数のシグネチャ変更
以前は func TestXxx() bool のようにブール値を返していたテスト関数が、func TestXxx(t *testing.T) のように *testing.T を引数として受け取るように変更されました。これにより、テスト関数内で t.Error(), t.Fatal() などのメソッドを呼び出してテスト結果を報告できるようになりました。
テストランナーの変更
testing.Main 関数は、*testing.T を使用するように更新されました。各テスト関数は新しいゴルーチンで実行され、TRunner 関数が testing.T インスタンスを管理し、テストの実行結果をチャネルを通じて Main 関数に報告します。これにより、テストの並列実行や、個々のテストの失敗が他のテストに影響を与えないような分離が可能になりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルで行われています。
-
src/lib/testing.go:testing.T構造体の定義と、そのメソッド(Fail,FailNow,Log,Logf,Error,Errorf,Fatal,Fatalf)の実装が追加されました。Test構造体のfフィールドの型が*() boolから*(*T)に変更されました。これは、テスト関数が*testing.Tを引数として受け取るようになったことを反映しています。TRunner関数が追加され、個々のテスト関数を*testing.Tインスタンスとともに実行する役割を担います。Main関数が大幅に修正され、testing.Tを使用してテストを実行し、結果を収集するように変更されました。各テストはゴルーチンで実行され、TRunnerを介してMainに結果が返されます。
-
src/lib/strconv/*.go(testatof.go, testatoi.go, testdecimal.go, testfp.go, testftoa.go, testitoa.go):- これらのファイル内のテスト関数(例:
XTestAtof,TestAtoui64,TestDecimalShiftなど)のシグネチャが、ブール値を返す形式から*testing.Tを引数として受け取る形式 (func XTestAtof(t *testing.T, opt bool)) に変更されました。 - テストの失敗を報告するために、以前は
fmt.printfを使用してok = falseとしていた箇所が、t.Errorfやt.Error、t.Fatalなど、testing.Tのメソッドを呼び出す形式に置き換えられました。 import "testing"が追加されました。
- これらのファイル内のテスト関数(例:
コアとなるコードの解説
src/lib/testing.go の変更
// testing.T 構造体の定義
export type T struct {
errors string;
failed bool;
ch *chan *T; // テストゴルーチンとメインランナー間の通信用チャネル
}
// Fail メソッド: テストを失敗としてマークするが、実行は継続
func (t *T) Fail() {
t.failed = true
}
// FailNow メソッド: テストを失敗としてマークし、即座に実行を停止
func (t *T) FailNow() {
t.Fail();
t.ch <- t; // T インスタンスをチャネルに送信してメインランナーに通知
sys.goexit(); // 現在のゴルーチンを終了
}
// Log メソッド: ログメッセージを記録
func (t *T) Log(args ...) {
t.errors += "\t" + fmt.sprintln(args);
}
// Error メソッド: ログを記録し、テストを失敗としてマーク
func (t *T) Error(args ...) {
t.Log(args);
t.Fail();
}
// Fatal メソッド: ログを記録し、テストを失敗としてマークし、即座に実行を停止
func (t *T) Fatal(args ...) {
t.Log(args);
t.FailNow();
}
// Test 構造体の更新: テスト関数が *testing.T を引数として受け取るように変更
export type Test struct {
name string;
f *(*T); // 以前は *() bool
}
// TRunner 関数: 個々のテストを新しいゴルーチンで実行し、結果をチャネルで返す
func TRunner(t *T, test *Test) {
test.f(t); // テスト関数を実行
t.ch <- t; // 実行結果の T インスタンスをチャネルに送信
}
// Main 関数: テストのメインランナー
export func Main(tests *[]Test) {
// ... (フラグ解析などの初期化) ...
ok := true; // 全体としてのテスト結果
for i := 0; i < len(tests); i++ {
// ... (テスト開始ログ) ...
t := new(T); // 新しい T インスタンスを作成
t.ch = new(chan *T); // T インスタンス用のチャネルを作成
go TRunner(t, &tests[i]); // 新しいゴルーチンでテストを実行
<-t.ch; // テストゴルーチンからの結果を待機
if t.failed { // テストが失敗した場合
println("--- FAIL:", tests[i].name);
print(t.errors); // エラーメッセージを出力
ok = false;
} else if chatty { // テストが成功し、chatty モードの場合
println("--- PASS:", tests[i].name);
print(t.errors); // ログメッセージを出力
}
}
if !ok {
println("FAIL");
sys.exit(1); // 失敗した場合は終了コード 1
}
println("PASS"); // 全て成功した場合は終了コード 0
}
src/lib/strconv/*.go の変更例 (src/lib/strconv/testatof.go から抜粋)
// testing パッケージのインポート
import (
"fmt";
"os";
"strconv";
"testing" // 追加
)
// テスト関数のシグネチャ変更: *testing.T を引数として受け取る
// 以前: func XTestAtof(opt bool) bool
func XTestAtof(t *testing.T, opt bool) {
oldopt := strconv.optimize;
strconv.optimize = opt;
// ok := true; // 以前はブール値で成否を管理していた
for i := 0; i < len(tests); i++ {
// ...
// エラー報告方法の変更: fmt.printf から t.Errorf へ
// 以前: fmt.printf("strconv.atof64(%v) = %v, %v want %v, %v\n", t.in, out, err, t.out, t.err); ok = false;
if outs != test.out || err != test.err {
t.Errorf("strconv.atof64(%v) = %v, %v want %v, %v\n",
test.in, out, err, test.out, test.err);
}
// ...
}
strconv.optimize = oldopt;
// return ok; // 以前はブール値を返していた
}
// export func TestAtof() bool { return XTestAtof(true); } // 以前
export func TestAtof(t *testing.T) { // 新しいシグネチャ
XTestAtof(t, true);
}
これらの変更により、Goのテストはより表現力豊かで、自動化に適した形に進化しました。testing.T の導入は、Goのテストフレームワークの基礎を築き、その後の発展(例: サブテスト、ベンチマークテストなど)を可能にしました。
関連リンク
- Go言語の公式ドキュメント (現在の
testingパッケージ): https://pkg.go.dev/testing - Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
参考にした情報源リンク
- Go言語の公式リポジトリのコミット履歴: https://github.com/golang/go/commit/6d30efc77215d97d57330ab2daaac338db388a17
- Go言語の初期の設計に関する議論やドキュメント (Goのメーリングリストや初期のGo Wikiなど、当時の情報源を辿ることでより深い理解が得られますが、直接的なリンクは時間の経過とともに失われている可能性があります。)
- Go言語の
testingパッケージの進化に関する記事やブログポスト (Goの歴史を解説しているもの):- "The Go Programming Language" by Alan A. A. Donovan and Brian W. Kernighan (Goのテストに関する章)
- "Go in Action" by Brian Ketelsen, Erik St. Martin, and William Kennedy (Goのテストに関する章)
- Goのブログ記事やカンファレンス発表 (特に初期のGoに関するもの)
- Goのテストに関するStack Overflowの議論やチュートリアル (
testing.Tの使い方に関するもの)