[インデックス 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
の使い方に関するもの)