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

[インデックス 17261] ファイルの概要

このコミットは、Go言語の標準ライブラリ testing パッケージに TB という新しいインターフェースを追加するものです。この TB インターフェースは、テスト関数に渡される *testing.T とベンチマーク関数に渡される *testing.B の両方に共通するメソッド群を定義しており、テストヘルパー関数などで TB の両方を受け入れられるようにすることで、コードの再利用性と柔軟性を向上させます。

コミット

commit 35d8bb39bd7953dddead3d97db32af77d8941563
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Aug 14 23:21:32 2013 -0700

    testing: add TB, an interface common to T and B
    
    R=golang-dev, kevlar, rsc, adg, r
    CC=golang-dev
    https://golang.org/cl/12962043
---
 src/pkg/database/sql/sql_test.go | 16 ++++------------
 src/pkg/testing/testing.go       | 27 +++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 12 deletions(-)

diff --git a/src/pkg/database/sql/sql_test.go b/src/pkg/database/sql/sql_test.go
index 693f5e3a3c..2a059da453 100644
--- a/src/pkg/database/sql/sql_test.go
+++ b/src/pkg/database/sql/sql_test.go
@@ -39,15 +39,7 @@ const fakeDBName = "foo"
 
 var chrisBirthday = time.Unix(123456789, 0)
 
-type testOrBench interface {
-	Fatalf(string, ...interface{})
-	Errorf(string, ...interface{})
-	Fatal(...interface{})
-	Error(...interface{})
-	Logf(string, ...interface{})
-}
-
-func newTestDB(t testOrBench, name string) *DB {
+func newTestDB(t testing.TB, name string) *DB {
 	db, err := Open("test", fakeDBName)
 	if err != nil {
 		t.Fatalf("Open: %v", err)
@@ -69,14 +61,14 @@ func newTestDB(t testOrBench, name string) *DB {
 	return db
 }
 
-func exec(t testOrBench, db *DB, query string, args ...interface{}) {
+func exec(t testing.TB, db *DB, query string, args ...interface{}) {
 	_, err := db.Exec(query, args...)
 	if err != nil {
 		t.Fatalf("Exec of %q: %v", query, err)
 	}\n}\n \n-func closeDB(t testOrBench, db *DB) {\n+func closeDB(t testing.TB, db *DB) {\n \tif e := recover(); e != nil {\n \t\tfmt.Printf("Panic: %v\\n", e)\n \t\tpanic(e)\n@@ -1061,7 +1053,7 @@ func TestStmtCloseOrder(t *testing.T) {\n \t}\n }\n \n-func manyConcurrentQueries(t testOrBench) {\n+func manyConcurrentQueries(t testing.TB) {\n \tmaxProcs, numReqs := 16, 500\n \tif testing.Short() {\n \t\tmaxProcs, numReqs = 4, 50
diff --git a/src/pkg/testing/testing.go b/src/pkg/testing/testing.go
index 852f4e7a62..4c81201a84 100644
--- a/src/pkg/testing/testing.go
+++ b/src/pkg/testing/testing.go
@@ -196,6 +196,31 @@ func decorate(s string) string {\n 	return buf.String()\n }\n \n+// TB is the interface common to T and B.\n+type TB interface {\n+\tError(args ...interface{})\n+\tErrorf(format string, args ...interface{})\n+\tFail()\n+\tFailNow()\n+\tFailed() bool\n+\tFatal(args ...interface{})\n+\tFatalf(format string, args ...interface{})\n+\tLog(args ...interface{})\n+\tLogf(format string, args ...interface{})\n+\tSkip(args ...interface{})\n+\tSkipNow()\n+\tSkipf(format string, args ...interface{})\n+\tSkipped() bool\n+\n+\t// A private method to prevent users implementing the\n+\t// interface and so future additions to it will not\n+\t// violate Go 1 compatibility.\n+\tprivate()\n+}\n+\n+var _ TB = (*T)(nil)\n+var _ TB = (*B)(nil)\n+\n // T is a type passed to Test functions to manage test state and support formatted test logs.\n // Logs are accumulated during execution and dumped to standard error when done.\n type T struct {\n@@ -204,6 +229,8 @@ type T struct {\n 	startParallel chan bool // Parallel tests will wait on this.\n }\n \n+func (c *common) private() {}\n+\n // Fail marks the function as having failed but continues execution.\n func (c *common) Fail() {\n \tc.mu.Lock()\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/35d8bb39bd7953dddead3d97db32af77d8941563](https://github.com/golang/go/commit/35d8bb39bd7953dddead3d97db32af77d8941563)

## 元コミット内容

testing: add TB, an interface common to T and B

R=golang-dev, kevlar, rsc, adg, r CC=golang-dev https://golang.org/cl/12962043


## 変更の背景

Go言語のテストフレームワークでは、通常のテストには `*testing.T` 型の引数が、ベンチマークテストには `*testing.B` 型の引数がそれぞれテスト関数に渡されます。これらの型は、テストの失敗報告、ログ出力、テストのスキップなどの共通の機能を提供します。しかし、これら2つの型は異なる型であるため、`*testing.T` と `*testing.B` の両方で動作するヘルパー関数を作成する場合、以前はそれぞれの型を引数として受け取る関数をオーバーロードするか、あるいは両方の型が共通して持つメソッドを明示的に定義したカスタムインターフェースを作成する必要がありました。

このコミット以前の `src/pkg/database/sql/sql_test.go` のコードでは、`testOrBench` というカスタムインターフェースが定義されており、`*testing.T` と `*testing.B` の両方で利用できる共通のメソッド(`Fatalf`, `Errorf`, `Fatal`, `Error`, `Logf`)を抽象化していました。これは、テストとベンチマークの両方で利用される `newTestDB`, `exec`, `closeDB`, `manyConcurrentQueries` といったヘルパー関数が、`*testing.T` と `*testing.B` のどちらのコンテキストでも動作するようにするための工夫でした。

しかし、このようなカスタムインターフェースを各パッケージで定義するのは冗長であり、`testing` パッケージ自体が提供するべき共通の抽象化が不足していることを示していました。このコミットは、この問題を解決し、`*testing.T` と `*testing.B` の共通機能を標準的なインターフェースとして `testing` パッケージに導入することで、テストコードの記述をより簡潔かつ統一的にすることを目的としています。

## 前提知識の解説

### Go言語の `testing` パッケージ

Go言語には、標準ライブラリとして `testing` パッケージが提供されており、ユニットテストやベンチマークテストを記述するための機能が含まれています。

*   **`*testing.T`**: 通常のテスト関数(`func TestXxx(t *testing.T)`)に渡される型です。テストの成功/失敗の報告、ログ出力、テストのスキップなどのメソッドを提供します。例えば、`t.Errorf()` でエラーを報告したり、`t.Fatalf()` でエラーを報告してテストを即座に終了させたりします。
*   **`*testing.B`**: ベンチマークテスト関数(`func BenchmarkXxx(b *testing.B)`)に渡される型です。ベンチマークの実行回数制御や、ベンチマーク結果の報告などのメソッドを提供します。`*testing.T` と同様に、エラー報告やログ出力のメソッドも持ちます。

### Go言語のインターフェース

Go言語のインターフェースは、メソッドのシグネチャの集合を定義する型です。Goのインターフェースは「暗黙的」に満たされます。つまり、ある型がインターフェースで定義されたすべてのメソッドを実装していれば、その型はそのインターフェースを満たしているとみなされ、明示的な宣言は不要です。

インターフェースは、異なる具体的な型に対して共通の振る舞いを抽象化するために使用されます。これにより、ポリモーフィズム(多態性)を実現し、より柔軟で再利用可能なコードを記述できます。例えば、`io.Reader` インターフェースは、ファイル、ネットワーク接続、メモリ上のバッファなど、様々なデータソースからの読み込み操作を抽象化します。

このコミットにおける `testing.TB` インターフェースの導入は、`*testing.T` と `*testing.B` という異なる具体的な型が持つ共通のテスト/ベンチマーク操作を抽象化し、これらの型を統一的に扱えるようにするためのものです。

## 技術的詳細

このコミットの主要な変更点は、`src/pkg/testing/testing.go` に `TB` インターフェースが追加されたことです。

`TB` インターフェースは、`*testing.T` と `*testing.B` の両方が共通して持つ、テストやベンチマークの実行中に状態を報告したり、ログを出力したり、実行を制御したりするためのメソッド群を定義しています。具体的には、以下のメソッドが含まれます。

*   `Error(args ...interface{})`
*   `Errorf(format string, args ...interface{})`
*   `Fail()`
*   `FailNow()`
*   `Failed() bool`
*   `Fatal(args ...interface{})`
*   `Fatalf(format string, args ...interface{})`
*   `Log(args ...interface{})`
*   `Logf(format string, args ...interface{})`
*   `Skip(args ...interface{})`
*   `SkipNow()`
*   `Skipf(format string, args ...interface{})`
*   `Skipped() bool`

これらのメソッドは、テストやベンチマークの失敗を報告したり、ログメッセージを出力したり、テストの実行をスキップしたりするために使用されます。

### Go 1 互換性への配慮 (`private()` メソッド)

`TB` インターフェースには、`private()` というメソッドが追加されています。このメソッドは、Go 1 の互換性保証を維持するための特殊なメカニズムです。

Go 1 の互換性原則では、公開されたAPI(エクスポートされた型、関数、メソッドなど)は、将来のバージョンで変更されないことが保証されます。しかし、インターフェースに新しいメソッドを追加すると、そのインターフェースを実装していた既存の型がその新しいメソッドを実装していない場合、互換性が損なわれる可能性があります。

`private()` メソッドは、`testing` パッケージ内部でのみ実装される非公開メソッドです。ユーザーが `TB` インターフェースを独自に実装しようとしても、この非公開メソッドを実装できないため、`TB` インターフェースを完全に満たすことはできません。これにより、Goチームは将来的に `TB` インターフェースに新しい公開メソッドを追加する必要が生じた場合でも、既存のユーザーコードが `TB` インターフェースを「完全に」実装していると見なされることを防ぎ、Go 1 互換性を損なうことなくインターフェースを拡張できる柔軟性を確保しています。

### インターフェースの実装確認

コミットでは、以下の行が追加されています。

```go
var _ TB = (*T)(nil)
var _ TB = (*B)(nil)

これは、Goの慣用的な方法で、*testing.T 型と *testing.B 型がそれぞれ TB インターフェースを正しく実装していることをコンパイル時に確認するためのものです。_ (ブランク識別子) を使用することで、変数を宣言しつつもその値を使用しないことを示し、コンパイラが未使用変数エラーを発生させないようにします。もし *testing.T*testing.BTB インターフェースのいずれかのメソッドを実装していなかった場合、この行でコンパイルエラーが発生し、実装の不整合を早期に検出できます。

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

このコミットによるコードの変更は主に以下の2つのファイルに集中しています。

  1. src/pkg/database/sql/sql_test.go:

    • 既存のカスタムインターフェース testOrBench が削除されました。
    • testOrBench を引数として受け取っていたヘルパー関数 (newTestDB, exec, closeDB, manyConcurrentQueries) のシグネチャが変更され、新しく導入された testing.TB を引数として受け取るようになりました。これにより、これらのヘルパー関数は *testing.T*testing.B の両方で直接利用できるようになります。
  2. src/pkg/testing/testing.go:

    • TB インターフェースが新しく定義されました。このインターフェースは、*testing.T*testing.B に共通するメソッド群(Error, Errorf, Fail, FailNow, Failed, Fatal, Fatalf, Log, Logf, Skip, SkipNow, Skipf, Skipped)を含みます。
    • Go 1 互換性を維持するための非公開メソッド private()TB インターフェースに追加され、*common 型(*testing.T*testing.B の基底となる共通構造体)にその実装が追加されました。
    • *testing.T*testing.BTB インターフェースを実装していることをコンパイル時に確認するための var _ TB = (*T)(nil)var _ TB = (*B)(nil) の行が追加されました。

コアとなるコードの解説

src/pkg/database/sql/sql_test.go の変更

変更前:

type testOrBench interface {
	Fatalf(string, ...interface{})
	Errorf(string, ...interface{})
	Fatal(...interface{})
	Error(...interface{})
	Logf(string, ...interface{})
}

func newTestDB(t testOrBench, name string) *DB {
    // ...
}
// 他のヘルパー関数も同様

変更後:

// testOrBench インターフェースは削除された

func newTestDB(t testing.TB, name string) *DB {
    // ...
}
// 他のヘルパー関数も testing.TB を引数に取るように変更

この変更により、database/sql パッケージのテストコード内で独自に定義されていた testOrBench インターフェースが不要になりました。代わりに、標準の testing.TB インターフェースを使用することで、コードの重複が解消され、Go言語の標準的なテストAPIに準拠する形になりました。これにより、newTestDB などのヘルパー関数は、*testing.T*testing.B のどちらのインスタンスも直接受け取れるようになり、より汎用的に利用できるようになります。

src/pkg/testing/testing.go の変更

追加された TB インターフェースの定義:

// TB is the interface common to T and B.
type TB interface {
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Fail()
	FailNow()
	Failed() bool
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	Log(args ...interface{})
	Logf(format string, args ...interface{})
	Skip(args ...interface{})
	SkipNow()
	Skipf(format string, args ...interface{})
	Skipped() bool

	// A private method to prevent users implementing the
	// interface and so future additions to it will not
	// violate Go 1 compatibility.
	private()
}

この定義は、*testing.T*testing.B が共通して提供するすべての主要なメソッドを網羅しています。これにより、これらのメソッドを必要とする任意のヘルパー関数は、testing.TB を引数として受け取ることで、TB の両方のコンテキストで動作する汎用的なコードとして記述できるようになります。

private() メソッドの追加と、*common 型へのその実装:

func (c *common) private() {}

*testing.T*testing.B は内部的に common 構造体を埋め込んでいるため、commonprivate() メソッドを実装することで、両方の型が自動的に TB インターフェースの private() メソッドを満たすことになります。この非公開メソッドの目的は、前述の通り、Go 1 互換性を維持しつつ将来的なインターフェースの拡張を可能にすることです。

インターフェース実装のコンパイル時チェック:

var _ TB = (*T)(nil)
var _ TB = (*B)(nil)

これらの行は、*testing.T*testing.BTB インターフェースのすべてのメソッドを正しく実装していることを、コンパイル時に静的に検証するためのものです。これにより、開発者はインターフェースの定義と実装の間に不整合がないことを保証できます。

関連リンク

参考にした情報源リンク

  • Go.dev: testing.TB interface (https://pkg.go.dev/testing#TB)
  • Stack Overflow: What is the purpose of testing.TB in Go? (https://stackoverflow.com/questions/24600007/what-is-the-purpose-of-testing-tb-in-go)
  • Konrad Reiche's Blog: Go: testing.TB (https://konradreiche.com/go-testing-tb/)
  • Go 1 Compatibility Guarantee (https://go.dev/doc/go1compat)
  • Go Code Review Comments: Interfaces (https://go.dev/wiki/CodeReviewComments#interfaces)
  • Go Code Review Comments: Blank Identifier (https://go.dev/wiki/CodeReviewComments#blank-identifier)