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

[インデックス 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 を使用してテスト結果やエラーメッセージをコンソールに出力していました。
  • panicrecover: Goのエラー処理メカニズムの一つで、プログラムの異常終了を引き起こす panic と、それを捕捉して回復する recover があります。testing.TFailNowFatal メソッドは、内部的に panic を利用してテストの即時終了を実現しています。
  • goexit(): runtime パッケージ(Go 1.0以前は sys パッケージ)の関数で、現在のゴルーチンを終了させますが、プログラム全体は終了させません。FailNow メソッドでテストゴルーチンを終了させるために使用されます。

技術的詳細

このコミットの主要な技術的変更点は、src/lib/testing.gotesting.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(): 現在のテストを失敗としてマークし、テストの実行を即座に停止します。これは、テストがこれ以上続行できない致命的なエラーに遭遇した場合に使用されます。内部的には panicsys.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 関数に報告します。これにより、テストの並列実行や、個々のテストの失敗が他のテストに影響を与えないような分離が可能になりました。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主に以下のファイルで行われています。

  1. 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 に結果が返されます。
  2. 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.Errorft.Errort.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言語の公式リポジトリのコミット履歴: 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 の使い方に関するもの)