[インデックス 1196] ファイルの概要
このコミットは、Go言語の標準ライブラリであるregexp
パッケージのテストフレームワークを、Goの標準的なテストツールであるgotest
とtesting
パッケージに移行するものです。これにより、テストの実行方法が統一され、Goエコシステム全体でのテストの整合性が向上します。
コミット
commit be7e0f8160f173844108a537a988b800c558e652
Author: Rob Pike <r@golang.org>
Date: Wed Nov 19 15:38:46 2008 -0800
gotestify regexp
R=rsc
DELTA=101 (53 added, 25 deleted, 23 changed)
OCL=19635
CL=19637
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/be7e0f8160f173844108a537a988b800c558e652
元コミット内容
このコミットの元のコミットメッセージは「gotestify regexp」です。これは、regexp
パッケージのテストをgotest
というツールに対応させることを意味しています。R=rsc
はレビュー担当者がRuss Coxであることを示し、DELTA
は変更された行数(追加、削除、変更)の合計を示しています。OCL
とCL
は、当時のGo開発で使われていた内部的な変更リスト番号です。
変更の背景
この変更が行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。Go言語は、その設計思想の一つとして、シンプルで効率的な開発ツールを提供することを目指していました。初期のGoプロジェクトでは、各パッケージが独自のMakefileやテストスクリプトを持つことが一般的でしたが、これはテストの実行方法に一貫性がなく、開発者にとって学習コストとなる可能性がありました。
このコミットの背景には、Go言語の標準的なテストフレームワークとツール(testing
パッケージとgotest
コマンド)を確立し、それらを既存のライブラリに適用していくという方針があったと考えられます。regexp
パッケージはGoの基本的な機能の一部であり、そのテストが標準的な方法で実行できることは、Go言語全体の品質と開発体験を向上させる上で重要でした。
具体的には、以前はregexp
パッケージのテストは、main
パッケージとしてコンパイルされ、カスタムのMakefileターゲットを通じて実行されていました。これは、Goのテストがまだ標準化されていなかった時代の名残です。このコミットは、Goのテストシステムが成熟し、testing
パッケージとgotest
コマンドが導入されたことに伴い、regexp
パッケージのテストもその新しい標準に準拠させるためのものです。
前提知識の解説
このコミットを理解するためには、以下のGo言語の基本的な概念と、当時のGo開発環境に関する知識が必要です。
- Go言語のパッケージシステム: Goのコードはパッケージにまとめられます。
main
パッケージは実行可能なプログラムのエントリポイントであり、それ以外のパッケージはライブラリとして機能します。テストコードは通常、テスト対象のパッケージと同じディレクトリに配置され、パッケージ名も同じになります(ただし、テストファイルは_test.go
というサフィックスを持ちます)。 - Goのビルドシステム: Goは、
go build
やgo install
といったコマンドを通じて、ソースコードをコンパイルし、実行可能ファイルやライブラリを生成します。初期のGoでは、Makefile
がビルドプロセスを制御するためによく使われていました。 - Goのテストフレームワーク (
testing
パッケージ): Go言語には、標準ライブラリとしてtesting
パッケージが提供されています。このパッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストなどを記述するための機能を提供します。- テスト関数は、
Test
で始まり、*testing.T
型の引数を一つ取る必要があります(例:func TestMyFunction(t *testing.T)
)。 - テストの失敗を報告するには、
t.Error()
やt.Fatalf()
などのメソッドを使用します。 - ログ出力には
t.Log()
を使用します。
- テスト関数は、
gotest
コマンド:gotest
は、Goの標準的なテスト実行ツールです。testing
パッケージで記述されたテスト関数を自動的に発見し、実行します。このコマンドは、テストの実行、結果の集計、カバレッジレポートの生成などを行います。6cov
ツール:6cov
は、Goの初期のコードカバレッジツールです。Goのコンパイラが生成するプロファイリング情報(6
は当時のGoのコンパイラ名である6g
に由来)を解析し、テストによって実行されたコードの割合を報告します。- Goの初期のツールチェインの命名規則: 2008年当時のGoのツールチェインは、ターゲットアーキテクチャとOSに基づいて命名されていました。例えば、
6g
はPlan 9の6
アーキテクチャ(x86)向けのGoコンパイラ、6l
はリンカ、6a
はアセンブラを指していました。このコミットのMakefileに見られるA=6
,G=$(A)g
,L=$(A)l
といった記述は、これらのツールを指しています。
技術的詳細
このコミットの技術的な変更は、主に以下の2つのファイルにわたっています。
src/lib/regexp/Makefile
の変更
- ビルドツールの変数定義の変更:
- 変更前:
A=6
,G=$(A)g
,L=$(A)l
- 変更後:
O=6
,GC=$(O)g
,CC=$(O)c -w
,AS=$(O)a
,AR=$(O)ar
これは、Goのビルドツール(コンパイラ、リンカ、アセンブラ、アーカイバ)の呼び出し方法を、より汎用的で明示的な変数名に変更したものです。GC
はGoコンパイラ、CC
はCコンパイラ(Goのランタイムの一部はCで書かれていたため)、AS
はアセンブラ、AR
はアーカイバを指します。
- 変更前:
- テストターゲットの変更:
- 変更前:
test: main.$A test.$A; $L -o test test.$A; ./test
- これは、
main.6
とtest.6
(Goのオブジェクトファイル)をリンクしてtest
という実行可能ファイルを生成し、それを直接実行するというカスタムのテスト実行フローでした。
- これは、
- 変更後:
test: packages; gotest
- これは、
packages
ターゲット(Goパッケージのビルド)を実行した後、gotest
コマンドを呼び出すように変更されています。gotest
は、Goの標準的なテストランナーであり、testing
パッケージに準拠したテスト関数を自動的に発見して実行します。
- これは、
- 変更前:
- カバレッジターゲットの追加:
coverage: packages; gotest; 6cov -g
pwd| grep -v '^test.*\.go:'
6cov
は、Goの初期のコードカバレッジツールです。このターゲットは、gotest
でテストを実行した後、6cov
を使ってカバレッジレポートを生成するように設定されています。grep
コマンドは、テストファイル自体のカバレッジ情報を除外するためのものです。
- Goソースファイルのコンパイルルールの変更:
- 変更前:
%.6: %.go; $G $<
- 変更後:
%.$(O): %.go; $(GC) $*.go
- Goソースファイル(
.go
)からGoオブジェクトファイル(.6
または.$(O)
)を生成するルールが、新しい変数名に合わせて更新されています。
- Goソースファイル(
- 変更前:
- パッケージのビルドとインストールの変更:
regexp.a
(Goのアーカイブライブラリ)のビルド方法が、より標準的なar
コマンドを使ったものに変更されています。install
ターゲットも、regexp.a
をGOROOT/pkg/regexp.a
にコピーするように変更され、packages
ターゲットに依存するようになりました。
src/lib/regexp/test.go
の変更
- パッケージ名の変更:
- 変更前:
package main
- 変更後:
package regexp
- これは最も重要な変更点の一つです。テストファイルが
main
パッケージではなく、テスト対象のregexp
パッケージの一部として扱われるようになりました。これにより、gotest
がこのファイルをテストファイルとして認識し、regexp
パッケージのコンテキストでテストを実行できるようになります。
- これは最も重要な変更点の一つです。テストファイルが
- 変更前:
testing
パッケージのインポート:import ("os"; "regexp"; "testing";)
- Goの標準テストフレームワークである
testing
パッケージがインポートされました。
- Goの標準テストフレームワークである
- テストヘルパー関数のシグネチャ変更:
Compile
,PrintVec
,Match
といったテストヘルパー関数が、*testing.T
型の引数を取るように変更されました。- 例:
func Compile(expr string, error *os.Error) regexp.Regexp
からfunc CompileTest(t *testing.T, expr string, error *os.Error) regexp.Regexp
- 例:
- これにより、これらの関数内でテストの失敗を報告したり、ログを出力したりする際に、
testing.T
のメソッド(t.Error
,t.Log
)を使用できるようになります。
- エラー報告とログ出力の変更:
print
やsys.exit(1)
といった直接的な出力やプログラム終了の代わりに、t.Error()
やt.Log()
が使用されるようになりました。t.Error()
はテストの失敗を報告しますが、テストの実行は継続します。t.Log()
はテストの実行中に情報を出力します。
- これにより、テストが失敗しても、他のテストが引き続き実行され、より詳細なテスト結果が得られるようになります。
main
関数の削除とテスト関数の追加:- 変更前は、
main
関数がテストの実行ロジックを含んでいました。 - 変更後、
main
関数は削除され、代わりにGoのテスト関数命名規則(Test
プレフィックス)に従った関数が追加されました。export func TestGoodCompile(t *testing.T)
export func TestBadCompile(t *testing.T)
export func TestMatch(t *testing.T)
- これらの関数は
gotest
によって自動的に発見され、実行されます。export
キーワードは、当時のGoのテストシステムで、テスト関数を外部に公開するために使われていた可能性があります(後のGoでは不要になります)。
- 変更前は、
コアとなるコードの変更箇所
このコミットのコアとなるコードの変更は、src/lib/regexp/test.go
におけるテスト関数のシグネチャ変更と、main
関数の削除、そしてtesting
パッケージの導入です。これにより、テストの実行方法がGoの標準的なgotest
コマンドとtesting
パッケージに準拠するようになりました。
また、src/lib/regexp/Makefile
におけるtest
ターゲットの変更も同様にコアな変更です。カスタムのテスト実行ロジックからgotest
への移行を直接的に示しています。
コアとなるコードの解説
src/lib/regexp/test.go
// 変更前: package main
// 変更後: package regexp
package regexp
import (
"os";
"regexp";
"testing"; // testingパッケージのインポート
)
// Compile関数のシグネチャ変更
// 変更前: func Compile(expr string, error *os.Error) regexp.Regexp
// 変更後: func CompileTest(t *testing.T, expr string, error *os.Error) regexp.Regexp
func CompileTest(t *testing.T, expr string, error *os.Error) regexp.Regexp {
re, err := regexp.Compile(expr);
if err != error {
// 変更前: print("compiling `", expr, "`; unexpected error: ", err.String(), "\n"); sys.exit(1);
// 変更後: t.Error("compiling `", expr, "`; unexpected error: ", err.String());
t.Error("compiling `", expr, "`; unexpected error: ", err.String());
}
return re
}
// PrintVec関数のシグネチャ変更
// 変更前: func PrintVec(m *[] int) {
// 変更後: func PrintVec(t *testing.T, m *[] int) {
func PrintVec(t *testing.T, m *[] int) {
l := MarkedLen(m);
if l == 0 {
// 変更前: print("<no match>");
// 変更後: t.Log("\t<no match>");
t.Log("\t<no match>");
} else {
for i := 0; i < l && m[i] != END; i = i+2 {
// 変更前: print(m[i], ",", m[i+1], " ")
// 変更後: t.Log("\t", m[i], ",", m[i+1])
t.Log("\t", m[i], ",", m[i+1])
}
}
}
// Match関数のシグネチャ変更
// 変更前: func Match(expr string, str string, match *[]int) {
// 変更後: func MatchTest(t *testing.T, expr string, str string, match *[]int) {
func MatchTest(t *testing.T, expr string, str string, match *[]int) {
// CompileTestを呼び出すように変更
re := CompileTest(t, expr, nil);
if re == nil {
return
}
m := re.Execute(str);
if !Equal(m, match) {
// 変更前: 複数のprintとsys.exit(1)
// 変更後: t.Errorとt.Log
t.Error("failure on `", expr, "` matching `", str, "`:");
PrintVec(t, m);
t.Log("should be:");
PrintVec(t, match);
}
}
// main関数の削除とテスト関数の追加
// 変更前: func main() { ... }
// 変更後:
export func TestGoodCompile(t *testing.T) {
for i := 0; i < len(good_re); i++ {
CompileTest(t, good_re[i], nil);
}
}
export func TestBadCompile(t *testing.T) {
for i := 0; i < len(bad_re); i++ {
CompileTest(t, bad_re[i].re, bad_re[i].err)
}
}
export func TestMatch(t *testing.T) {
for i := 0; i < len(matches); i++ {
test := &matches[i];
MatchTest(t, test.re, test.text, &test.match)
}
}
このコードの変更は、Goのテストのベストプラクティスに準拠するためのものです。*testing.T
を引数として受け取ることで、テストのコンテキスト(エラー報告、ログ出力など)をtesting
パッケージに委ねることができます。また、TestXxx
という命名規則に従うことで、gotest
コマンドが自動的にこれらのテスト関数を発見し、実行できるようになります。これにより、テストの実行が標準化され、開発者は特定のMakefileコマンドを覚える必要がなくなります。
src/lib/regexp/Makefile
# DO NOT EDIT. Automatically generated by gobuild.
# gobuild -m >Makefile
O=6
GC=$(O)g
CC=$(O)c -w
AS=$(O)a
AR=$(O)ar
default: packages
clean:
rm -f *.$O *.a $O.out
test: packages
gotest # ここが変更の核心。gotestコマンドを使用。
coverage: packages
gotest
6cov -g `pwd` | grep -v '^test.*\.go:' # 6covによるカバレッジレポート生成
%.$O: %.go
$(GC) $*.go
regexp.a: a1
a1: $(O1)
$(AR) grc regexp.a regexp.$O
rm -f $(O1)
newpkg: clean
$(AR) grc regexp.a
$(O1): newpkg
packages: regexp.a
install: packages
cp regexp.a $(GOROOT)/pkg/regexp.a
nuke: clean
rm -f $(GOROOT)/pkg/regexp.a
Makefileの変更は、テストの実行方法をgotest
に切り替えるためのものです。これにより、Goのビルドシステムとテストシステムがより密接に連携するようになり、開発者はgo test
コマンド(または当時のgotest
)一つでテストを実行できるようになります。また、6cov
を使ったカバレッジレポートの生成も統合され、テストプロセスがより包括的になりました。
関連リンク
- Go言語の
testing
パッケージのドキュメント(現在のものですが、基本的な概念は共通しています): https://pkg.go.dev/testing - Go言語の正規表現パッケージ
regexp
のドキュメント: https://pkg.go.dev/regexp
参考にした情報源リンク
- Go言語の初期の歴史に関する情報(
gotest
や6cov
の文脈を理解するのに役立ちます):- The Go Programming Language (2009): https://go.dev/doc/go_for_cpp_programmers.html (Goの初期の設計思想やツールに関する記述がある場合があります)
- Goのコミット履歴やメーリングリストのアーカイブ(当時の議論や決定の背景を深く掘り下げる場合)
- Goのビルドシステムに関する情報(Makefileの変更の背景を理解するのに役立ちます):
- Go Command Documentation (現在のものですが、
go build
の歴史的背景を推測するのに役立ちます): https://go.dev/cmd/go/
- Go Command Documentation (現在のものですが、
(注: 2008年当時の正確なドキュメントやブログ記事を見つけるのは困難な場合があります。上記のリンクは現在のGoのドキュメントですが、当時の設計思想やツールの進化を理解する上で参考になります。)