[インデックス 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
パッケージ