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

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

このコミットは、Go言語の標準ライブラリであるregexpパッケージのテストフレームワークを、Goの標準的なテストツールであるgotesttestingパッケージに移行するものです。これにより、テストの実行方法が統一され、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は変更された行数(追加、削除、変更)の合計を示しています。OCLCLは、当時のGo開発で使われていた内部的な変更リスト番号です。

変更の背景

この変更が行われた2008年11月は、Go言語がまだ一般に公開される前の初期開発段階でした。Go言語は、その設計思想の一つとして、シンプルで効率的な開発ツールを提供することを目指していました。初期のGoプロジェクトでは、各パッケージが独自のMakefileやテストスクリプトを持つことが一般的でしたが、これはテストの実行方法に一貫性がなく、開発者にとって学習コストとなる可能性がありました。

このコミットの背景には、Go言語の標準的なテストフレームワークとツール(testingパッケージとgotestコマンド)を確立し、それらを既存のライブラリに適用していくという方針があったと考えられます。regexpパッケージはGoの基本的な機能の一部であり、そのテストが標準的な方法で実行できることは、Go言語全体の品質と開発体験を向上させる上で重要でした。

具体的には、以前はregexpパッケージのテストは、mainパッケージとしてコンパイルされ、カスタムのMakefileターゲットを通じて実行されていました。これは、Goのテストがまだ標準化されていなかった時代の名残です。このコミットは、Goのテストシステムが成熟し、testingパッケージとgotestコマンドが導入されたことに伴い、regexpパッケージのテストもその新しい標準に準拠させるためのものです。

前提知識の解説

このコミットを理解するためには、以下のGo言語の基本的な概念と、当時のGo開発環境に関する知識が必要です。

  1. Go言語のパッケージシステム: Goのコードはパッケージにまとめられます。mainパッケージは実行可能なプログラムのエントリポイントであり、それ以外のパッケージはライブラリとして機能します。テストコードは通常、テスト対象のパッケージと同じディレクトリに配置され、パッケージ名も同じになります(ただし、テストファイルは_test.goというサフィックスを持ちます)。
  2. Goのビルドシステム: Goは、go buildgo installといったコマンドを通じて、ソースコードをコンパイルし、実行可能ファイルやライブラリを生成します。初期のGoでは、Makefileがビルドプロセスを制御するためによく使われていました。
  3. Goのテストフレームワーク (testingパッケージ): Go言語には、標準ライブラリとしてtestingパッケージが提供されています。このパッケージは、ユニットテスト、ベンチマークテスト、例(Example)テストなどを記述するための機能を提供します。
    • テスト関数は、Testで始まり、*testing.T型の引数を一つ取る必要があります(例: func TestMyFunction(t *testing.T))。
    • テストの失敗を報告するには、t.Error()t.Fatalf()などのメソッドを使用します。
    • ログ出力にはt.Log()を使用します。
  4. gotestコマンド: gotestは、Goの標準的なテスト実行ツールです。testingパッケージで記述されたテスト関数を自動的に発見し、実行します。このコマンドは、テストの実行、結果の集計、カバレッジレポートの生成などを行います。
  5. 6covツール: 6covは、Goの初期のコードカバレッジツールです。Goのコンパイラが生成するプロファイリング情報(6は当時のGoのコンパイラ名である6gに由来)を解析し、テストによって実行されたコードの割合を報告します。
  6. 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.6test.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))を生成するルールが、新しい変数名に合わせて更新されています。
  • パッケージのビルドとインストールの変更:
    • regexp.a(Goのアーカイブライブラリ)のビルド方法が、より標準的なarコマンドを使ったものに変更されています。
    • installターゲットも、regexp.aGOROOT/pkg/regexp.aにコピーするように変更され、packagesターゲットに依存するようになりました。

src/lib/regexp/test.goの変更

  • パッケージ名の変更:
    • 変更前: package main
    • 変更後: package regexp
      • これは最も重要な変更点の一つです。テストファイルがmainパッケージではなく、テスト対象のregexpパッケージの一部として扱われるようになりました。これにより、gotestがこのファイルをテストファイルとして認識し、regexpパッケージのコンテキストでテストを実行できるようになります。
  • testingパッケージのインポート:
    • import ("os"; "regexp"; "testing";)
      • Goの標準テストフレームワークであるtestingパッケージがインポートされました。
  • テストヘルパー関数のシグネチャ変更:
    • 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)を使用できるようになります。
  • エラー報告とログ出力の変更:
    • printsys.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言語の初期の歴史に関する情報(gotest6covの文脈を理解するのに役立ちます):
    • 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/

(注: 2008年当時の正確なドキュメントやブログ記事を見つけるのは困難な場合があります。上記のリンクは現在のGoのドキュメントですが、当時の設計思想やツールの進化を理解する上で参考になります。)