[インデックス 1238] ファイルの概要
このコミットは、Go言語のテストインフラストラクチャにおける重要な変更を示しています。具体的には、テストファイルの配置をtest/
ディレクトリから対応するライブラリのsrc/lib/
ディレクトリ内へ移動・整理し、Goのテストツールであるgotest
のテスト選択ロジックを洗練しています。これにより、テストコードがテスト対象のコードとより密接に配置されるようになり、テストの発見と実行がより効率的かつGoの慣習に沿った形に進化しました。
コミット
commit 0f83fa3a0c306d6eb56535540a4103104bb963ac
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 24 15:17:47 2008 -0800
convert tests.
refine gotest's test selection criteria.
R=r
DELTA=1590 (745 added, 844 deleted, 1 changed)
OCL=19903
CL=19936
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0f83fa3a0c306d6eb56535540a4103104bb963ac
元コミット内容
convert tests.
refine gotest's test selection criteria.
変更の背景
このコミットが行われた2008年後半は、Go言語がまだ初期開発段階にあり、その設計と標準ライブラリの構造が活発に進化していた時期です。初期のGoプロジェクトでは、テストコードが独立したtest/
ディレクトリに配置されることが一般的でした。しかし、これはテスト対象のコードとテストコードの間に物理的な距離を生み出し、プロジェクトが大規模になるにつれてテストの管理や関連性の把握を困難にする可能性がありました。
この変更の主な背景には、以下の点が挙げられます。
- テストのコ・ロケーション(Co-location): Go言語の設計思想として、関連するコードは同じパッケージ内に配置するという原則があります。テストコードも例外ではなく、テスト対象のパッケージと同じディレクトリに配置することで、コードの可読性、保守性、発見性を向上させることができます。このコミットは、この原則をテストにも適用し、
_test.go
という命名規則を導入することで、テストファイルを標準ライブラリの各パッケージに統合する第一歩となりました。 gotest
ツールの進化:gotest
はGo言語のテスト実行ツールであり、その機能はGoの進化とともに洗練されていきました。初期のgotest
はテスト関数の選択ロジックが比較的単純でしたが、このコミットでは、より厳密な正規表現を用いてテスト関数を識別するように改善されています。これにより、意図しない関数がテストとして実行されることを防ぎ、テストの信頼性と効率性を高めることを目指しています。- Go言語の標準ライブラリの成熟: Goの標準ライブラリが形成されていく過程で、各パッケージが自己完結的でテスト可能であることが重要視されました。テストコードを各パッケージに移動することで、パッケージごとのテストの独立性が高まり、CI/CDパイプラインでのテスト実行も容易になります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の初期の概念とツールに関する知識が役立ちます。
- Go言語のパッケージシステム: Goのコードはパッケージ(
package
キーワードで定義)に組織されます。関連する機能は同じパッケージにまとめられ、他のパッケージからインポートして利用されます。このコミットでは、テストコードも特定のパッケージに属するように変更されています。 _test.go
ファイル: Go言語のテストフレームワークの慣習として、テストコードはテスト対象のパッケージと同じディレクトリに配置され、ファイル名が_test.go
で終わる必要があります。これにより、Goツールチェーンは自動的にこれらのファイルをテストとして認識し、ビルド時に通常の実行可能ファイルとは別に扱います。gotest
コマンド: Go言語の公式テストツールです。go test
コマンドの前身にあたります。このツールは、指定されたパッケージ内の_test.go
ファイルを見つけ、その中のテスト関数(TestXxx
という命名規則に従う関数)を実行します。6nm
コマンド: Go言語の初期のツールチェーンの一部で、オブジェクトファイル(.6
ファイルなど)からシンボルテーブルをダンプするために使用されました。C言語のnm
コマンドに相当します。このコミットでは、gotest
スクリプト内で6nm
を使用して、コンパイルされたテストバイナリからテスト関数(T .*·Test
パターンにマッチするシンボル)を抽出しています。·
はGoの初期のツールチェーンでパッケージパスとシンボル名を区切るために使われていた特殊文字です。panic
とtesting.T
: Goの初期のテストでは、テスト失敗時にpanic
を発生させていました。しかし、Goのテストフレームワークが成熟するにつれて、testing
パッケージが導入され、*testing.T
型のメソッド(例:t.Errorf
,t.Fatalf
)を使用してテスト結果を報告するようになりました。これにより、テストの失敗がより詳細に、かつ制御された方法で扱えるようになりました。このコミットは、テストコードをtesting
パッケージの慣習に合わせる移行の一部でもあります。
技術的詳細
このコミットは、主に以下の3つの技術的側面で変更を加えています。
-
gotest
スクリプトのテスト選択ロジックの改善:- 以前は、
6nm
の出力からT .*·Test
というパターンでテスト関数を抽出していました。これは、Test
で始まるすべてのシンボルをテスト関数と見なすものでした。 - 変更後、パターンは
T .*·Test[A-Z]
となり、さらにgrep -v '·.*[.·]'
が追加されました。Test[A-Z]
は、テスト関数名がTest
の後に大文字で始まることを強制します。これにより、例えばTestHelper
のようなヘルパー関数が誤ってテストとして認識されることを防ぎます。grep -v '·.*[.·]'
は、シンボル名に複数のドット(·
)が含まれるものを除外します。これは、メソッド(例:MyType.TestSomething
)やその他の特殊な名前空間を持つシンボルがテスト関数として誤って選択されるのを防ぐためのものです。Goの初期のシンボル名では、パッケージ名と関数名、あるいは型名とメソッド名が·
で区切られていました。このフィルタリングにより、トップレベルのテスト関数のみが選択されるようになります。
- 以前は、
-
テストファイルの移動とリネーム:
test/bufiolib.go
がsrc/lib/bufio_test.go
にリネームされ、移動されました。これは、bufio
パッケージのテストが、そのパッケージのソースコードと同じsrc/lib/
ディレクトリ内に配置されるようになったことを意味します。- 同様に、
test/sorting.go
、test/stringslib.go
、test/timelib.go
が削除され、それぞれsrc/lib/sort_test.go
、src/lib/strings_test.go
、src/lib/time/time_test.go
として新しく作成されています。これにより、Goの標準ライブラリの各パッケージが自身のテストを内包する構造へと移行しました。
-
テストコード自体の変更:
- パッケージ宣言の変更: 多くのテストファイルで
package main
からテスト対象のパッケージ名(例:package bufio
)に変更されています。これは、テストが独立した実行可能ファイルとしてではなく、テスト対象のパッケージの一部としてコンパイル・実行されることを示しています。 main()
関数の削除: テストファイルからmain()
関数が削除されています。これは、gotest
(後のgo test
)がテストのエントリポイントを自動的に生成するため、手動でmain
関数を定義する必要がなくなったことを意味します。panic
からt.Errorf
への移行: テスト失敗時のエラー報告が、panic
からtesting.T
型のメソッド(t.Errorf
)を使用する形式に変更されています。これにより、テストの実行が途中で中断されることなく、複数のテスト結果をまとめて報告できるようになります。また、t.Errorf
はより詳細なエラーメッセージ(テスト名、期待値、実際の結果など)を提供できるようになります。- テスト関数の
export
キーワード:export func TestXxx
という形式でテスト関数が定義されています。これはGoの初期の構文で、関数がパッケージ外から参照可能であることを示していました。後のGoのバージョンでは、関数名が大文字で始まることで自動的にエクスポートされるため、export
キーワードは不要になります。
- パッケージ宣言の変更: 多くのテストファイルで
これらの変更は、Go言語のテストフレームワークが、より現代的でGoらしい慣習(コ・ロケーション、testing
パッケージの利用、自動テスト発見)へと進化していく過程の重要な一歩でした。
コアとなるコードの変更箇所
src/cmd/gotest/gotest
におけるテスト選択ロジックの変更
--- a/src/cmd/gotest/gotest
+++ b/src/cmd/gotest/gotest
@@ -55,7 +55,10 @@ trap "rm -f _testmain.go _testmain.6" 0 1 2 3 14 15
# test array
echo
echo 'var tests = &[]testing.Test {'
- for i in $(6nm -s $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
+ # test functions are named TestFoo
+ # the grep -v eliminates methods and other special names
+ # that have multiple dots.
+ for i in $(6nm -s $ofiles | grep ' T .*·Test[A-Z]' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./')
do
echo ' testing.Test{ "'$i'", &'$i' },'
done
test/bufiolib.go
からsrc/lib/bufio_test.go
への変更(一部抜粋)
--- a/test/bufiolib.go
+++ b/src/lib/bufio_test.go
@@ -2,16 +2,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// $G $F.go && $L $F.$A && ./$A.out
-
-package main
+package bufio
import (
- "os";
- "io";
"bufio";
+ "fmt";
+ "io";
+ "os";
"syscall";
- "rand"
+ "testing";
)
func StringToBytes(s string) *[]byte {
@@ -186,39 +182,19 @@ var bufsizes = []int {
23, 32, 46, 64, 93, 128, 1024, 4096
}
-func TestBufRead() {
- // work around 6g bug101
- readmakers[0] = &NewByteReader;
- readmakers[1] = &NewHalfByteReader;
-
- bufreaders[0] = &Read1;
- bufreaders[1] = &Read2;
- bufreaders[2] = &Read3;
- bufreaders[3] = &Read4;
- bufreaders[4] = &Read5;
- bufreaders[5] = &Read7;
- bufreaders[6] = &ReadBytes;
- bufreaders[7] = &ReadLines;
-
- bufsizes[0] = 1;
- bufsizes[1] = 2;
- bufsizes[2] = 3;
- bufsizes[3] = 4;
- bufsizes[4] = 5;
- bufsizes[5] = 6;
- bufsizes[6] = 7;
- bufsizes[7] = 8;
- bufsizes[8] = 9;
- bufsizes[9] = 10;
- bufsizes[10] = 23;
- bufsizes[11] = 32;
- bufsizes[12] = 46;
- bufsizes[13] = 64;
- bufsizes[14] = 93;
- bufsizes[15] = 128;
- bufsizes[16] = 1024;
- bufsizes[17] = 4096;
+export func TestBufReadSimple(t *testing.T) {
+ b, e := NewBufRead(NewByteReader(StringToBytes("hello world")));
+ if s := ReadBytes(b); s != "hello world" {
+ t.Errorf("simple hello world test failed: got %q", s);
+ }
+
+ b, e = NewBufRead(NewRot13Reader(NewByteReader(StringToBytes("hello world"))));
+ if s := ReadBytes(b); s != "uryyb jbeyq" {
+ t.Error("rot13 hello world test failed: got %q", s);
+ }
+}
+export func TestBufRead(t *testing.T) {
var texts [31]string;
str := "";
all := "";
@@ -229,33 +205,21 @@ func TestBufRead() {
}
texts[len(texts)-1] = all;
- // BUG 6g should not need nbr temporary (bug099)
- nbr := NewByteReader(StringToBytes("hello world"));
- b, e := bufio.NewBufRead(nbr);
- if ReadBytes(b) != "hello world" { panic("hello world") }
-
- // BUG 6g should not need nbr nor nbr1 (bug009)
- nbr = NewByteReader(StringToBytes("hello world"));
- nbr1 := NewRot13Reader(nbr);
- b, e = bufio.NewBufRead(nbr1);
- if ReadBytes(b) != "uryyb jbeyq" { panic("hello world") }
-
for h := 0; h < len(texts); h++ {
text := texts[h];
textbytes := StringToBytes(text);
for i := 0; i < len(readmakers); i++ {
- readmaker := readmakers[i];
for j := 0; j < len(bufreaders); j++ {
- bufreader := bufreaders[j];
for k := 0; k < len(bufsizes); k++ {
+ readmaker := readmakers[i];
+ bufreader := bufreaders[j];
bufsize := bufsizes[k];
- read := readmaker(textbytes);
- buf, e := bufio.NewBufReadSize(read, bufsize);
- s := bufreader(buf);
+ read := readmaker.fn(textbytes);
+ buf, e := NewBufReadSize(read, bufsize);
+ s := bufreader.fn(buf);
if s != text {
- print("Failed: ", h, " ", i, " ", j, " ", k, " ", len(s), " ", len(text), "\n");
- print("<", s, ">\nshould be <", text, ">\n");
- panic("bufio result")
+ t.Errorf("reader=%s fn=%s bufsize=%d want=%q got=%q",
+ readmaker.name, bufreader.name, bufsize, text, s);
}
}
}
@@ -370,8 +324,3 @@ func TestBufWrite() {
}
}
-
-func main() {
- TestBufRead();
- TestBufWrite()
-}
コアとなるコードの解説
src/cmd/gotest/gotest
の変更
このシェルスクリプトは、Goのテストバイナリからテスト関数を抽出し、それらをtesting.Test
構造体の配列として_testmain.go
というファイルに書き出す役割を担っています。この_testmain.go
が、最終的にテストを実行するメイン関数を含むバイナリとしてコンパイルされます。
変更前は、grep ' T .*·Test'
という正規表現で、シンボル名にTest
を含むすべてのシンボルをテスト関数として認識していました。これは、例えばTest
という文字列を含むだけのヘルパー関数なども誤ってテストとして扱ってしまう可能性がありました。
変更後は、grep ' T .*·Test[A-Z]' | grep -v '·.*[.·]'
というより厳密なフィルタリングが導入されました。
grep ' T .*·Test[A-Z]'
:Test
の直後に大文字が続くシンボルのみを対象とします。これはGoのテスト関数の命名規則(例:TestMyFeature
)に厳密に合致させ、Test
で始まるがテスト関数ではないシンボル(例:TestUtil
のようなヘルパー関数)を除外します。grep -v '·.*[.·]'
: シンボル名に複数のドット(·
)が含まれる行を除外します。Goの初期のシンボル名では、メソッドはType·Method
のように表現され、パッケージ内のグローバル関数とは異なる形式でした。このフィルタリングにより、gotest
はトップレベルのテスト関数(例:package.TestFunction
)のみを抽出し、メソッド(例:package.Type·Method
)が誤ってテストとして実行されるのを防ぎます。
この変更により、gotest
はより正確にテスト関数を識別できるようになり、テスト実行の信頼性と効率が向上しました。
test/bufiolib.go
からsrc/lib/bufio_test.go
への変更
このファイルは、bufio
パッケージのテストコードです。変更は、Goのテストフレームワークの進化と、テストコードの配置に関する慣習の変化を明確に示しています。
-
パッケージ宣言の変更:
- 変更前:
package main
- 変更後:
package bufio
これは最も重要な変更点の一つです。以前は、テストファイルが独立した実行可能ファイル(main
パッケージ)としてコンパイルされ、main()
関数からテストが手動で呼び出されていました。変更後は、テストファイルがテスト対象のbufio
パッケージの一部として扱われるようになり、gotest
(またはgo test
)が自動的にテスト関数を検出し、実行するようになりました。これにより、テストの記述と実行が大幅に簡素化されます。
- 変更前:
-
main()
関数の削除:- 変更前は、ファイルの最後に
main()
関数があり、その中でTestBufRead()
やTestBufWrite()
といったテスト関数を明示的に呼び出していました。 - 変更後は、
main()
関数が完全に削除されています。これは、Goのテストツールが自動的にテスト関数を検出し、実行するためのエントリポイントを生成するため、ユーザーが手動でmain
関数を記述する必要がなくなったためです。
- 変更前は、ファイルの最後に
-
エラー報告の変更(
panic
からt.Errorf
へ):- 変更前は、テストが失敗した場合に
panic()
を呼び出してプログラムを強制終了させていました。これは、テストが一つ失敗するとそれ以降のテストが実行されないという問題がありました。 - 変更後、
testing
パッケージがインポートされ、テスト関数は*testing.T
型の引数t
を受け取るようになりました。テスト失敗時にはt.Errorf()
(またはt.Error()
)が呼び出されます。t.Errorf()
は、エラーメッセージを記録しますが、テストの実行を即座に停止させません。これにより、一つのテスト関数内で複数のアサーションが失敗した場合でも、すべて報告されるようになります。- また、
t.Errorf("reader=%s fn=%s bufsize=%d want=%q got=%q", ...)
のように、より詳細なコンテキスト情報(どのリーダー、どの関数、バッファサイズ、期待値、実際の結果)を含むエラーメッセージを出力できるようになり、デバッグが容易になります。
- 変更前は、テストが失敗した場合に
-
テスト関数の命名と
export
:TestBufReadSimple
やTestBufRead
、TestBufWrite
といった関数がexport func
として定義されています。これはGoの初期の構文で、関数が外部から参照可能であることを示していました。Goのテストツールは、Test
で始まり、その後に大文字が続く関数を自動的にテスト関数として認識します。
これらの変更は、Go言語のテストが、より堅牢で、情報量が多く、自動化されたフレームワークへと移行する過程を示しており、現代のGo開発におけるテストのベストプラクティスの基礎を築きました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
testing
パッケージ: https://pkg.go.dev/testing (現在のドキュメントですが、基本的な概念は共通しています) - Go言語の初期の歴史に関する情報源 (例: Goのブログ記事、カンファレンストークなど)
参考にした情報源リンク
- Go言語の公式Gitリポジトリ: https://github.com/golang/go
- コミットハッシュ:
0f83fa3a0c306d6eb56535540a4103104bb963ac
のdiff情報 - Go言語の初期のツールチェーンに関する情報 (例:
6nm
コマンドの役割など) - これは一般的なWeb検索やGoの歴史に関する記事から得られる情報です。 - Go言語のテストの進化に関する記事やドキュメント。
[インデックス 1238] ファイルの概要
このコミットは、Go言語のテストインフラストラクチャにおける重要な変更を示しています。具体的には、テストファイルの配置をtest/
ディレクトリから対応するライブラリのsrc/lib/
ディレクトリ内へ移動・整理し、Goのテストツールであるgotest
のテスト選択ロジックを洗練しています。これにより、テストコードがテスト対象のコードとより密接に配置されるようになり、テストの発見と実行がより効率的かつGoの慣習に沿った形に進化しました。
コミット
commit 0f83fa3a0c306d6eb56535540a4103104bb963ac
Author: Russ Cox <rsc@golang.org>
Date: Mon Nov 24 15:17:47 2008 -0800
convert tests.
refine gotest's test selection criteria.
R=r
DELTA=1590 (745 added, 844 deleted, 1 changed)
OCL=19903
CL=19936
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0f83fa3a0c306d6eb56535540a4103104bb963ac
元コミット内容
convert tests.
refine gotest's test selection criteria.
変更の背景
このコミットが行われた2008年後半は、Go言語がまだ初期開発段階にあり、その設計と標準ライブラリの構造が活発に進化していた時期です。初期のGoプロジェクトでは、テストコードが独立したtest/
ディレクトリに配置されることが一般的でした。しかし、これはテスト対象のコードとテストコードの間に物理的な距離を生み出し、プロジェクトが大規模になるにつれてテストの管理や関連性の把握を困難にする可能性がありました。
この変更の主な背景には、以下の点が挙げられます。
- テストのコ・ロケーション(Co-location): Go言語の設計思想として、関連するコードは同じパッケージ内に配置するという原則があります。テストコードも例外ではなく、テスト対象のパッケージと同じディレクトリに配置することで、コードの可読性、保守性、発見性を向上させることができます。このコミットは、この原則をテストにも適用し、
_test.go
という命名規則を導入することで、テストファイルを標準ライブラリの各パッケージに統合する第一歩となりました。 gotest
ツールの進化:gotest
はGo言語のテスト実行ツールであり、その機能はGoの進化とともに洗練されていきました。初期のgotest
はテスト関数の選択ロジックが比較的単純でしたが、このコミットでは、より厳密な正規表現を用いてテスト関数を識別するように改善されています。これにより、意図しない関数がテストとして実行されることを防ぎ、テストの信頼性と効率性を高めることを目指しています。- Go言語の標準ライブラリの成熟: Goの標準ライブラリが形成されていく過程で、各パッケージが自己完結的でテスト可能であることが重要視されました。テストコードを各パッケージに移動することで、パッケージごとのテストの独立性が高まり、CI/CDパイプラインでのテスト実行も容易になります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の初期の概念とツールに関する知識が役立ちます。
- Go言語のパッケージシステム: Goのコードはパッケージ(
package
キーワードで定義)に組織されます。関連する機能は同じパッケージにまとめられ、他のパッケージからインポートして利用されます。このコミットでは、テストコードも特定のパッケージに属するように変更されています。 _test.go
ファイル: Go言語のテストフレームワークの慣習として、テストコードはテスト対象のパッケージと同じディレクトリに配置され、ファイル名が_test.go
で終わる必要があります。これにより、Goツールチェーンは自動的にこれらのファイルをテストとして認識し、ビルド時に通常の実行可能ファイルとは別に扱います。gotest
コマンド: Go言語の公式テストツールです。go test
コマンドの前身にあたります。このツールは、指定されたパッケージ内の_test.go
ファイルを見つけ、その中のテスト関数(TestXxx
という命名規則に従う関数)を実行します。6nm
コマンド: Go言語の初期のツールチェーンの一部で、オブジェクトファイル(.6
ファイルなど)からシンボルテーブルをダンプするために使用されました。C言語のnm
コマンドに相当します。このコミットでは、gotest
スクリプト内で6nm
を使用して、コンパイルされたテストバイナリからテスト関数(T .*·Test
パターンにマッチするシンボル)を抽出しています。·
はGoの初期のツールチェーンでパッケージパスとシンボル名を区切るために使われていた特殊文字です。panic
とtesting.T
: Goの初期のテストでは、テスト失敗時にpanic
を発生させていました。しかし、Goのテストフレームワークが成熟するにつれて、testing
パッケージが導入され、*testing.T
型のメソッド(例:t.Errorf
,t.Fatalf
)を使用してテスト結果を報告するようになりました。これにより、テストの失敗がより詳細に、かつ制御された方法で扱えるようになりました。このコミットは、テストコードをtesting
パッケージの慣習に合わせる移行の一部でもあります。
技術的詳細
このコミットは、主に以下の3つの技術的側面で変更を加えています。
-
gotest
スクリプトのテスト選択ロジックの改善:- 以前は、
6nm
の出力からT .*·Test
というパターンでテスト関数を抽出していました。これは、Test
で始まるすべてのシンボルをテスト関数と見なすものでした。 - 変更後、パターンは
T .*·Test[A-Z]
となり、さらにgrep -v '·.*[.·]'
が追加されました。Test[A-Z]
は、テスト関数名がTest
の後に大文字で始まることを強制します。これにより、例えばTestHelper
のようなヘルパー関数が誤ってテストとして認識されることを防ぎます。grep -v '·.*[.·]'
は、シンボル名に複数のドット(·
)が含まれるものを除外します。これは、メソッド(例:MyType.TestSomething
)やその他の特殊な名前空間を持つシンボルがテスト関数として誤って選択されるのを防ぐためのものです。Goの初期のシンボル名では、パッケージ名と関数名、あるいは型名とメソッド名が·
で区切られていました。このフィルタリングにより、トップレベルのテスト関数のみが選択されるようになります。
- 以前は、
-
テストファイルの移動とリネーム:
test/bufiolib.go
がsrc/lib/bufio_test.go
にリネームされ、移動されました。これは、bufio
パッケージのテストが、そのパッケージのソースコードと同じsrc/lib/
ディレクトリ内に配置されるようになったことを意味します。- 同様に、
test/sorting.go
、test/stringslib.go
、test/timelib.go
が削除され、それぞれsrc/lib/sort_test.go
、src/lib/strings_test.go
、src/lib/time/time_test.go
として新しく作成されています。これにより、Goの標準ライブラリの各パッケージが自身のテストを内包する構造へと移行しました。
-
テストコード自体の変更:
- パッケージ宣言の変更: 多くのテストファイルで
package main
からテスト対象のパッケージ名(例:package bufio
)に変更されています。これは、テストが独立した実行可能ファイルとしてではなく、テスト対象のパッケージの一部としてコンパイル・実行されることを示しています。 main()
関数の削除: テストファイルからmain()
関数が削除されています。これは、gotest
(後のgo test
)がテストのエントリポイントを自動的に生成するため、手動でmain
関数を定義する必要がなくなったことを意味します。panic
からt.Errorf
への移行: テスト失敗時のエラー報告が、panic
からtesting.T
型のメソッド(t.Errorf
)を使用する形式に変更されています。これにより、テストの実行が途中で中断されることなく、複数のテスト結果をまとめて報告できるようになります。また、t.Errorf
はより詳細なエラーメッセージ(テスト名、期待値、実際の結果など)を提供できるようになります。- テスト関数の
export
キーワード:export func TestXxx
という形式でテスト関数が定義されています。これはGoの初期の構文で、関数がパッケージ外から参照可能であることを示していました。後のGoのバージョンでは、関数名が大文字で始まることで自動的にエクスポートされるため、export
キーワードは不要になります。
- パッケージ宣言の変更: 多くのテストファイルで
これらの変更は、Go言語のテストフレームワークが、より現代的でGoらしい慣習(コ・ロケーション、testing
パッケージの利用、自動テスト発見)へと進化していく過程の重要な一歩でした。
コアとなるコードの変更箇所
src/cmd/gotest/gotest
におけるテスト選択ロジックの変更
--- a/src/cmd/gotest/gotest
+++ b/src/cmd/gotest/gotest
@@ -55,7 +55,10 @@ trap "rm -f _testmain.go _testmain.6" 0 1 2 3 14 15
# test array
echo
echo 'var tests = &[]testing.Test {'
- for i in $(6nm -s $ofiles | grep ' T .*·Test' | sed 's/.* //; s/·/./')
+ # test functions are named TestFoo
+ # the grep -v eliminates methods and other special names
+ # that have multiple dots.
+ for i in $(6nm -s $ofiles | grep ' T .*·Test[A-Z]' | grep -v '·.*[.·]' | sed 's/.* //; s/·/./')
do
echo ' testing.Test{ "'$i'", &'$i' },'
done
test/bufiolib.go
からsrc/lib/bufio_test.go
への変更(一部抜粋)
--- a/test/bufiolib.go
+++ b/src/lib/bufio_test.go
@@ -2,16 +2,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// $G $F.go && $L $F.$A && ./$A.out
-
-package main
+package bufio
import (
- "os";
- "io";
"bufio";
+ "fmt";
+ "io";
+ "os";
"syscall";
- "rand"
+ "testing";
)
func StringToBytes(s string) *[]byte {
@@ -186,39 +182,19 @@ var bufsizes = []int {
23, 32, 46, 64, 93, 128, 1024, 4096
}
-func TestBufRead() {
- // work around 6g bug101
- readmakers[0] = &NewByteReader;
- readmakers[1] = &NewHalfByteReader;
-
- bufreaders[0] = &Read1;
- bufreaders[1] = &Read2;
- bufreaders[2] = &Read3;
- bufreaders[3] = &Read4;
- bufreaders[4] = &Read5;
- bufreaders[5] = &Read7;
- bufreaders[6] = &ReadBytes;
- bufreaders[7] = &ReadLines;
-
- bufsizes[0] = 1;
- bufsizes[1] = 2;
- bufsizes[2] = 3;
- bufsizes[3] = 4;
- bufsizes[4] = 5;
- bufsizes[5] = 6;
- bufsizes[6] = 7;
- bufsizes[7] = 8;
- bufsizes[8] = 9;
- bufsizes[9] = 10;
- bufsizes[10] = 23;
- bufsizes[11] = 32;
- bufsizes[12] = 46;
- bufsizes[13] = 64;
- bufsizes[14] = 93;
- bufsizes[15] = 128;
- bufsizes[16] = 1024;
- bufsizes[17] = 4096;
+export func TestBufReadSimple(t *testing.T) {
+ b, e := NewBufRead(NewByteReader(StringToBytes("hello world")));
+ if s := ReadBytes(b); s != "hello world" {
+ t.Errorf("simple hello world test failed: got %q", s);
+ }
+
+ b, e = NewBufRead(NewRot13Reader(NewByteReader(StringToBytes("hello world"))));
+ if s := ReadBytes(b); s != "uryyb jbeyq" {
+ t.Error("rot13 hello world test failed: got %q", s);
+ }
+}
+export func TestBufRead(t *testing.T) {
var texts [31]string;
str := "";
all := "";
@@ -229,33 +205,21 @@ func TestBufRead() {
}
texts[len(texts)-1] = all;
- // BUG 6g should not need nbr temporary (bug099)
- nbr := NewByteReader(StringToBytes("hello world"));
- b, e := bufio.NewBufRead(nbr);
- if ReadBytes(b) != "hello world" { panic("hello world") }
-
- // BUG 6g should not need nbr nor nbr1 (bug009)
- nbr = NewByteReader(StringToBytes("hello world"));
- nbr1 := NewRot13Reader(nbr);
- b, e = bufio.NewBufRead(nbr1);
- if ReadBytes(b) != "uryyb jbeyq" { panic("hello world") }
-
for h := 0; h < len(texts); h++ {
text := texts[h];
textbytes := StringToBytes(text);
for i := 0; i < len(readmakers); i++ {
- readmaker := readmakers[i];
for j := 0; j < len(bufreaders); j++ {
- bufreader := bufreaders[j];
for k := 0; k < len(bufsizes); k++ {
+ readmaker := readmakers[i];
+ bufreader := bufreaders[j];
bufsize := bufsizes[k];
- read := readmaker(textbytes);
- buf, e := bufio.NewBufReadSize(read, bufsize);
- s := bufreader(buf);
+ read := readmaker.fn(textbytes);
+ buf, e := NewBufReadSize(read, bufsize);
+ s := bufreader.fn(buf);
if s != text {
- print("Failed: ", h, " ", i, " ", j, " ", k, " ", len(s), " ", len(text), "\n");
- print("<", s, ">\nshould be <", text, ">\n");
- panic("bufio result")
+ t.Errorf("reader=%s fn=%s bufsize=%d want=%q got=%q",
+ readmaker.name, bufreader.name, bufsize, text, s);
}
}
}
@@ -370,8 +324,3 @@ func TestBufWrite() {
}
}
-
-func main() {
- TestBufRead();
- TestBufWrite()
-}
コアとなるコードの解説
src/cmd/gotest/gotest
の変更
このシェルスクリプトは、Goのテストバイナリからテスト関数を抽出し、それらをtesting.Test
構造体の配列として_testmain.go
というファイルに書き出す役割を担っています。この_testmain.go
が、最終的にテストを実行するメイン関数を含むバイナリとしてコンパイルされます。
変更前は、grep ' T .*·Test'
という正規表現で、シンボル名にTest
を含むすべてのシンボルをテスト関数として認識していました。これは、例えばTest
という文字列を含むだけのヘルパー関数なども誤ってテストとして扱ってしまう可能性がありました。
変更後は、grep ' T .*·Test[A-Z]' | grep -v '·.*[.·]'
というより厳密なフィルタリングが導入されました。
grep ' T .*·Test[A-Z]'
:Test
の直後に大文字が続くシンボルのみを対象とします。これはGoのテスト関数の命名規則(例:TestMyFeature
)に厳密に合致させ、Test
で始まるがテスト関数ではないシンボル(例:TestUtil
のようなヘルパー関数)を除外します。grep -v '·.*[.·]'
: シンボル名に複数のドット(·
)が含まれる行を除外します。Goの初期のシンボル名では、メソッドはType·Method
のように表現され、パッケージ内のグローバル関数とは異なる形式でした。このフィルタリングにより、gotest
はトップレベルのテスト関数(例:package.TestFunction
)のみを抽出し、メソッド(例:package.Type·Method
)が誤ってテストとして実行されるのを防ぎます。
この変更により、gotest
はより正確にテスト関数を識別できるようになり、テスト実行の信頼性と効率が向上しました。
test/bufiolib.go
からsrc/lib/bufio_test.go
への変更
このファイルは、bufio
パッケージのテストコードです。変更は、Goのテストフレームワークの進化と、テストコードの配置に関する慣習の変化を明確に示しています。
-
パッケージ宣言の変更:
- 変更前:
package main
- 変更後:
package bufio
これは最も重要な変更点の一つです。以前は、テストファイルが独立した実行可能ファイル(main
パッケージ)としてコンパイルされ、main()
関数からテストが手動で呼び出されていました。変更後は、テストファイルがテスト対象のbufio
パッケージの一部として扱われるようになり、gotest
(またはgo test
)が自動的にテスト関数を検出し、実行するようになりました。これにより、テストの記述と実行が大幅に簡素化されます。
- 変更前:
-
main()
関数の削除:- 変更前は、ファイルの最後に
main()
関数があり、その中でTestBufRead()
やTestBufWrite()
といったテスト関数を明示的に呼び出していました。 - 変更後は、
main()
関数が完全に削除されています。これは、Goのテストツールが自動的にテスト関数を検出し、実行するためのエントリポイントを生成するため、ユーザーが手動でmain
関数を記述する必要がなくなったためです。
- 変更前は、ファイルの最後に
-
エラー報告の変更(
panic
からt.Errorf
へ):- 変更前は、テストが失敗した場合に
panic()
を呼び出してプログラムを強制終了させていました。これは、テストが一つ失敗するとそれ以降のテストが実行されないという問題がありました。 - 変更後、
testing
パッケージがインポートされ、テスト関数は*testing.T
型の引数t
を受け取るようになりました。テスト失敗時にはt.Errorf()
(またはt.Error()
)が呼び出されます。t.Errorf()
は、エラーメッセージを記録しますが、テストの実行を即座に停止させません。これにより、一つのテスト関数内で複数のアサーションが失敗した場合でも、すべて報告されるようになります。- また、
t.Errorf("reader=%s fn=%s bufsize=%d want=%q got=%q", ...)
のように、より詳細なコンテキスト情報(どのリーダー、どの関数、バッファサイズ、期待値、実際の結果)を含むエラーメッセージを出力できるようになり、デバッグが容易になります。
- 変更前は、テストが失敗した場合に
-
テスト関数の命名と
export
:TestBufReadSimple
やTestBufRead
、TestBufWrite
といった関数がexport func
として定義されています。これはGoの初期の構文で、関数が外部から参照可能であることを示していました。Goのテストツールは、Test
で始まり、その後に大文字が続く関数を自動的にテスト関数として認識します。
これらの変更は、Go言語のテストが、より堅牢で、情報量が多く、自動化されたフレームワークへと移行する過程を示しており、現代のGo開発におけるテストのベストプラクティスの基礎を築きました。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- Go言語の
testing
パッケージ: https://pkg.go.dev/testing (現在のドキュメントですが、基本的な概念は共通しています) - Go言語の初期の歴史に関する情報源 (例: Goのブログ記事、カンファレンストークなど)
参考にした情報源リンク
- Go言語の公式Gitリポジトリ: https://github.com/golang/go
- コミットハッシュ:
0f83fa3a0c306d6eb56535540a4103104bb963ac
のdiff情報 - Go言語の初期のツールチェーンに関する情報 (例:
6nm
コマンドの役割など) - これは一般的なWeb検索やGoの歴史に関する記事から得られる情報です。 - Go言語のテストの進化に関する記事やドキュメント。