[インデックス 11234] ファイルの概要
このコミットは、Go言語のドキュメンテーション生成ツールである go/doc パッケージのテストフレームワークを全面的に書き直し、完成させたものです。これにより、テストの構造が改善され、より堅牢なテストが可能になりました。特に、テスト対象のパッケージとそれに対応する「ゴールデンファイル」(期待される出力を含むファイル)を ./testdata ディレクトリに配置する新しい仕組みが導入されました。ゴールデンファイルを更新するための go test -update コマンドも提供されています。
コミット
- コミットハッシュ:
39bb4bd454b915aed58d1732c6d7c6e3b233d706 - Author: Robert Griesemer gri@golang.org
- Date: Wed Jan 18 14:11:31 2012 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/39bb4bd454b915aed58d1732c6d7c6e3b233d706
元コミット内容
go/doc: rewrote and completed test framework
Packages to test are kept in ./testdata together
with the corresponding golden (packagename.out)
file.
To update the golden files, run: go test -update
R=rsc
CC=golang-dev
https://golang.org/cl/5543054
変更の背景
go/doc パッケージは、Goのソースコードからドキュメンテーションを生成するための重要なツールです。このパッケージの正確性と信頼性を保証するためには、包括的でメンテナンスしやすいテストフレームワークが不可欠です。以前のテストフレームワークは、おそらく柔軟性や拡張性に欠けていたか、あるいはゴールデンファイルのような自動比較メカニズムが不足していたと考えられます。
このコミットの背景には、以下の目的があったと推測されます。
- テストの堅牢性向上:
go/docの出力が期待通りであることを保証するため、ゴールデンファイルを用いた比較テストを導入し、テストの信頼性を高める。 - テストのメンテナンス性向上: テストケースの追加や更新を容易にするため、テストデータの管理方法を標準化し、
go test -updateのような便利なツールを提供する。 - テストフレームワークの整理: 既存のテストコードをリファクタリングし、よりクリーンで理解しやすい構造にする。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連技術の知識が必要です。
- Go言語のパッケージとドキュメンテーション (
go/doc):go/docパッケージは、Goのソースコードを解析し、その構造(パッケージ、関数、型、変数、定数など)からドキュメンテーションを生成する機能を提供します。これはgo docコマンドや pkg.go.dev で利用される基盤技術です。- ドキュメンテーションコメントは、Goのソースコード内で特定の形式で記述され、
go/docによって抽出・整形されます。
- Go言語のテストフレームワーク (
testingパッケージ):- Goには標準で
testingパッケージが用意されており、ユニットテスト、ベンチマークテスト、サンプルコードのテストなどを記述できます。 go testコマンドは、_test.goで終わるファイル内のTestXxx関数を実行します。- ゴールデンファイルテスト: テストの出力が事前に定義された「ゴールデンファイル」の内容と一致するかを比較するテスト手法です。これにより、出力の変更が意図したものであるか、あるいは予期せぬ回帰であるかを容易に検出できます。
go test -updateフラグ: ゴールデンファイルテストにおいて、テストの実行結果をゴールデンファイルに上書き保存するための慣習的なフラグです。これにより、期待される出力が変更された際に、手動でゴールデンファイルを更新する手間を省くことができます。
- Goには標準で
- Go言語のAST (Abstract Syntax Tree):
go/parserパッケージは、Goのソースコードを解析し、抽象構文木 (AST) を生成します。ASTはプログラムの構造を木構造で表現したもので、コンパイラやコード分析ツールがコードを理解するために使用します。go/astパッケージはASTのノード構造を定義します。go/tokenパッケージは、ソースコード内の位置情報(ファイル、行番号、列番号など)を管理します。
- Go言語のコード整形 (
go/printer):go/printerパッケージは、ASTをGoのソースコードとして整形して出力する機能を提供します。
- テキストテンプレート (
text/template):text/templateパッケージは、Goのデータ構造を元にテキストを生成するためのテンプレートエンジンを提供します。このコミットでは、ドキュメンテーションの出力を整形するために使用されています。
- ファイルシステム操作:
io/ioutil(Go 1.16以降はioとosに分割): ファイルの読み書きなどのI/O操作を提供します。os: オペレーティングシステムとのインタフェースを提供し、ファイル操作や環境変数へのアクセスなどを可能にします。path/filepath: ファイルパスを操作するためのユーティリティを提供します。
- 文字列操作 (
strings):- Goの標準ライブラリで、文字列の操作(分割、結合、置換など)を行うための機能を提供します。
- バイトスライス操作 (
bytes):- バイトスライスを操作するための機能を提供します。特に
bytes.Bufferは、効率的なバイトデータの構築によく使われます。
- バイトスライスを操作するための機能を提供します。特に
- コマンドラインフラグ (
flag):- コマンドライン引数を解析し、プログラム内で利用するための機能を提供します。このコミットでは
go test -updateの-updateフラグを処理するために使用されています。
- コマンドライン引数を解析し、プログラム内で利用するための機能を提供します。このコミットでは
- 時間操作 (
time):- 時間と期間を扱うための機能を提供します。ベンチマークテストなどで実行時間を計測する際に使用されます。
- ランタイム情報 (
runtime):- Goランタイムに関する情報(GC、GOMAXPROCSなど)を提供するパッケージです。
- プロファイリング (
runtime/pprof):- CPUプロファイルやメモリプロファイルなどのプロファイリング情報を提供するパッケージです。
技術的詳細
このコミットの主要な技術的変更点は、go/doc パッケージのテストが、従来の個別のテストケース定義から、testdata ディレクトリに配置された実際のGoパッケージのソースコードと、それに対応する期待される出力(ゴールデンファイル)を比較する方式に移行したことです。
新しいテストフレームワークの動作は以下の通りです。
- テストデータの配置:
src/pkg/go/doc/testdata/ディレクトリに、テスト対象となるGoパッケージのソースファイル(例:a0.go,b.go)と、それらのパッケージからgo/docが生成するドキュメンテーションの期待される出力を含むゴールデンファイル(例:a.out,b.out)が配置されます。 - テストの実行:
go testコマンドが実行されると、doc_test.go内のTest関数が呼び出されます。 - パッケージの解析:
Test関数はtestdataディレクトリ内のGoソースファイルをgo/parser.ParseDirを使用して解析し、ASTを構築します。 - ドキュメンテーションの生成: 解析されたASTから
go/doc.Newを使用してドキュメンテーション構造を生成します。 - 出力の整形: 生成されたドキュメンテーション構造は、
template.txtという新しいテンプレートファイルとtext/templateパッケージを使用して、特定の形式のテキスト出力に整形されます。このテンプレートは、パッケージ名、インポートパス、ファイル名、定数、変数、関数、型、バグ情報などを整形して出力します。 - ゴールデンファイルとの比較: 整形された出力は、対応するゴールデンファイル(例:
a.out)の内容と比較されます。- もし出力がゴールデンファイルと一致しない場合、テストは失敗し、差異が報告されます。
go test -updateフラグが指定されている場合、テストの出力はゴールデンファイルに上書き保存され、手動での更新が不要になります。
- ユーティリティ関数の導入:
nodeFmtやsynopsisFmtといったヘルパー関数が導入され、ASTノードの整形やドキュメンテーションの概要抽出を効率的に行えるようになりました。
このアプローチにより、テストケースの定義が簡素化され、実際のGoコードと期待されるドキュメンテーション出力の対応が明確になります。また、go test -update の導入により、ドキュメンテーションの生成ロジックが変更された際のテスト更新が容易になります。
コアとなるコードの変更箇所
src/pkg/go/doc/doc_test.go が大幅に書き換えられています。
削除された主要な要素:
type sources map[string]stringおよびtype testCase structの定義。register関数。testsマップ。runTest関数。docTextテンプレートおよび関連するPackage.String()メソッド。- 従来の個別の
var _ = register(&testCase{...})形式のテストケース定義。
追加・変更された主要な要素:
flagパッケージのインポートとvar update = flag.Bool("update", false, "update golden (.out) files")の追加。これによりgo test -updateフラグがサポートされます。const dataDir = "testdata"の定義。テストデータが配置されるディレクトリを示します。templateTxt変数とreadTemplate関数の追加。template.txtからテンプレートを読み込み、nodeとsynopsisというカスタム関数を登録します。nodeFmt関数の追加:go/printerを使用してASTノードを整形し、文字列として返すヘルパー関数。synopsisFmt関数の追加: ドキュメンテーション文字列から概要を抽出するヘルパー関数。isGoFile関数の追加: ファイルがGoソースファイルであるかを判定します。type bundle structの追加:PackageとFileSetをまとめる構造体で、テンプレートに渡すデータとして使用されます。Test関数の大幅な書き換え:token.NewFileSet()でFileSetを作成。parser.ParseDir(fset, dataDir, isGoFile, parser.ParseComments)を使用してtestdataディレクトリ内のGoパッケージを解析。- 各パッケージに対して
go/doc.Newでドキュメンテーションを生成。 templateTxt.Executeを使用してドキュメンテーションを整形し、bytes.Bufferに書き込む。*updateフラグがtrueの場合、整形された出力をゴールデンファイルに書き込む (ioutil.WriteFile)。*updateフラグがfalseの場合、ゴールデンファイルを読み込み (ioutil.ReadFile)、整形された出力とゴールデンファイルをbytes.Compareで比較する。- 比較結果が異なる場合、
t.Errorfでエラーを報告し、差異を出力する。
新しく追加されたファイル:
src/pkg/go/doc/testdata/a.out:aパッケージのゴールデン出力。src/pkg/go/doc/testdata/a0.go:aパッケージのソースファイルの一部。src/pkg/go/doc/testdata/a1.go:aパッケージのソースファイルの一部。src/pkg/go/doc/testdata/b.go:bパッケージのソースファイル。src/pkg/go/doc/testdata/b.out:bパッケージのゴールデン出力。src/pkg/go/doc/testdata/benchmark.go: ベンチマークテストの例を含むファイル。src/pkg/go/doc/testdata/example.go: サンプルコードのテストの例を含むファイル。src/pkg/go/doc/testdata/template.txt: ドキュメンテーション出力の整形に使用されるテンプレートファイル。src/pkg/go/doc/testdata/testing.go:testingパッケージの機能の一部を模倣したテストファイル。src/pkg/go/doc/testdata/testing.out:testingパッケージのゴールデン出力。
コアとなるコードの解説
新しい Test 関数は、go/doc パッケージのテストの心臓部です。
func Test(t *testing.T) {
// get all packages
fset := token.NewFileSet()
pkgs, err := parser.ParseDir(fset, dataDir, isGoFile, parser.ParseComments)
if err != nil {
t.Fatal(err)
}
// test all packages
for _, pkg := range pkgs {
importpath := dataDir + "/" + pkg.Name
doc := New(pkg, importpath, 0)
// print documentation
var buf bytes.Buffer
if err := templateTxt.Execute(&buf, bundle{doc, fset}); err != nil {
t.Error(err)
continue
}
got := buf.Bytes()
// update golden file if necessary
golden := filepath.Join(dataDir, pkg.Name+".out")
if *update {
err := ioutil.WriteFile(golden, got, 0644)
if err != nil {
t.Error(err)
}
continue
}
// get golden file
want, err := ioutil.ReadFile(golden)
if err != nil {
t.Error(err)
continue
}
// compare
if bytes.Compare(got, want) != 0 {
t.Errorf("package %s\n\tgot:\n%s\n\twant:\n%s", pkg.Name, got, want)
}
}
}
このコードは以下のステップで動作します。
FileSetの初期化:token.NewFileSet()は、ソースコード内の位置情報を管理するためのオブジェクトを作成します。- パッケージの解析:
parser.ParseDirは、testdataディレクトリ内のGoソースファイルを解析し、map[string]*ast.Package型のpkgsを返します。isGoFile関数は、どのファイルを解析対象とするかをフィルタリングします。parser.ParseCommentsオプションは、コメントもASTに含めるように指示します。 - 各パッケージのテスト:
pkgsマップをイテレートし、各Goパッケージに対して以下の処理を行います。go/docオブジェクトの生成:New(pkg, importpath, 0)を呼び出して、解析されたパッケージのASTからドキュメンテーションオブジェクトを生成します。importpathは、生成されるドキュメンテーションに表示されるインポートパスです。- ドキュメンテーションの整形:
templateTxt.Execute(&buf, bundle{doc, fset})は、template.txtで定義されたテンプレートを使用して、生成されたドキュメンテーションオブジェクト (doc) を整形し、bytes.Buffer(buf) に書き込みます。bundle構造体は、テンプレート内でdocとfsetの両方にアクセスできるようにするために使用されます。 - ゴールデンファイルの更新: コマンドラインで
-updateフラグが指定されている場合 (*updateがtrue)、整形された出力 (got) が対応するゴールデンファイル (.out拡張子を持つファイル) に書き込まれます。これは、ドキュメンテーションの生成ロジックが変更され、期待される出力も変更された場合に、手動でゴールデンファイルを更新する手間を省くための便利な機能です。 - ゴールデンファイルとの比較:
-updateフラグが指定されていない場合、整形された出力 (got) は、既存のゴールデンファイル (want) の内容と比較されます。bytes.Compareはバイトスライスを比較し、一致しない場合は0以外の値を返します。 - テスト結果の報告: 比較結果が異なる場合、
t.Errorfを使用してテスト失敗を報告し、実際の出力と期待される出力の差異を表示します。これにより、開発者は何が変更されたかを一目で確認できます。
この新しいテストフレームワークは、go/doc の出力の正確性を保証するための強力なメカニズムを提供し、将来の変更に対する回帰テストを容易にします。
関連リンク
参考にした情報源リンク
- Go言語公式ドキュメント:
go/docパッケージ - Go言語公式ドキュメント:
testingパッケージ - Go言語公式ドキュメント:
go/parserパッケージ - Go言語公式ドキュメント:
go/printerパッケージ - Go言語公式ドキュメント:
text/templateパッケージ - Go言語公式ドキュメント:
flagパッケージ - Go言語公式ドキュメント:
io/ioutilパッケージ (Go 1.16以降はioとosに分割) - Go言語公式ドキュメント:
osパッケージ - Go言語公式ドキュメント:
path/filepathパッケージ - Go言語公式ドキュメント:
stringsパッケージ - Go言語公式ドキュメント:
bytesパッケージ - Go言語公式ドキュメント:
timeパッケージ - Go言語公式ドキュメント:
runtimeパッケージ - Go言語公式ドキュメント:
runtime/pprofパッケージ