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

[インデックス 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のソースコードからドキュメンテーションを生成するための重要なツールです。このパッケージの正確性と信頼性を保証するためには、包括的でメンテナンスしやすいテストフレームワークが不可欠です。以前のテストフレームワークは、おそらく柔軟性や拡張性に欠けていたか、あるいはゴールデンファイルのような自動比較メカニズムが不足していたと考えられます。

このコミットの背景には、以下の目的があったと推測されます。

  1. テストの堅牢性向上: go/doc の出力が期待通りであることを保証するため、ゴールデンファイルを用いた比較テストを導入し、テストの信頼性を高める。
  2. テストのメンテナンス性向上: テストケースの追加や更新を容易にするため、テストデータの管理方法を標準化し、go test -update のような便利なツールを提供する。
  3. テストフレームワークの整理: 既存のテストコードをリファクタリングし、よりクリーンで理解しやすい構造にする。

前提知識の解説

このコミットを理解するためには、以下の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言語の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以降は ioos に分割): ファイルの読み書きなどの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パッケージのソースコードと、それに対応する期待される出力(ゴールデンファイル)を比較する方式に移行したことです。

新しいテストフレームワークの動作は以下の通りです。

  1. テストデータの配置: src/pkg/go/doc/testdata/ ディレクトリに、テスト対象となるGoパッケージのソースファイル(例: a0.go, b.go)と、それらのパッケージから go/doc が生成するドキュメンテーションの期待される出力を含むゴールデンファイル(例: a.out, b.out)が配置されます。
  2. テストの実行: go test コマンドが実行されると、doc_test.go 内の Test 関数が呼び出されます。
  3. パッケージの解析: Test 関数は testdata ディレクトリ内のGoソースファイルを go/parser.ParseDir を使用して解析し、ASTを構築します。
  4. ドキュメンテーションの生成: 解析されたASTから go/doc.New を使用してドキュメンテーション構造を生成します。
  5. 出力の整形: 生成されたドキュメンテーション構造は、template.txt という新しいテンプレートファイルと text/template パッケージを使用して、特定の形式のテキスト出力に整形されます。このテンプレートは、パッケージ名、インポートパス、ファイル名、定数、変数、関数、型、バグ情報などを整形して出力します。
  6. ゴールデンファイルとの比較: 整形された出力は、対応するゴールデンファイル(例: a.out)の内容と比較されます。
    • もし出力がゴールデンファイルと一致しない場合、テストは失敗し、差異が報告されます。
    • go test -update フラグが指定されている場合、テストの出力はゴールデンファイルに上書き保存され、手動での更新が不要になります。
  7. ユーティリティ関数の導入: nodeFmtsynopsisFmt といったヘルパー関数が導入され、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 からテンプレートを読み込み、nodesynopsis というカスタム関数を登録します。
  • nodeFmt 関数の追加: go/printer を使用してASTノードを整形し、文字列として返すヘルパー関数。
  • synopsisFmt 関数の追加: ドキュメンテーション文字列から概要を抽出するヘルパー関数。
  • isGoFile 関数の追加: ファイルがGoソースファイルであるかを判定します。
  • type bundle struct の追加: PackageFileSet をまとめる構造体で、テンプレートに渡すデータとして使用されます。
  • 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)
		}
	}
}

このコードは以下のステップで動作します。

  1. FileSet の初期化: token.NewFileSet() は、ソースコード内の位置情報を管理するためのオブジェクトを作成します。
  2. パッケージの解析: parser.ParseDir は、testdata ディレクトリ内のGoソースファイルを解析し、map[string]*ast.Package 型の pkgs を返します。isGoFile 関数は、どのファイルを解析対象とするかをフィルタリングします。parser.ParseComments オプションは、コメントもASTに含めるように指示します。
  3. 各パッケージのテスト: pkgs マップをイテレートし、各Goパッケージに対して以下の処理を行います。
    • go/doc オブジェクトの生成: New(pkg, importpath, 0) を呼び出して、解析されたパッケージのASTからドキュメンテーションオブジェクトを生成します。importpath は、生成されるドキュメンテーションに表示されるインポートパスです。
    • ドキュメンテーションの整形: templateTxt.Execute(&buf, bundle{doc, fset}) は、template.txt で定義されたテンプレートを使用して、生成されたドキュメンテーションオブジェクト (doc) を整形し、bytes.Buffer (buf) に書き込みます。bundle 構造体は、テンプレート内で docfset の両方にアクセスできるようにするために使用されます。
    • ゴールデンファイルの更新: コマンドラインで -update フラグが指定されている場合 (*updatetrue)、整形された出力 (got) が対応するゴールデンファイル (.out 拡張子を持つファイル) に書き込まれます。これは、ドキュメンテーションの生成ロジックが変更され、期待される出力も変更された場合に、手動でゴールデンファイルを更新する手間を省くための便利な機能です。
    • ゴールデンファイルとの比較: -update フラグが指定されていない場合、整形された出力 (got) は、既存のゴールデンファイル (want) の内容と比較されます。bytes.Compare はバイトスライスを比較し、一致しない場合は 0 以外の値を返します。
    • テスト結果の報告: 比較結果が異なる場合、t.Errorf を使用してテスト失敗を報告し、実際の出力と期待される出力の差異を表示します。これにより、開発者は何が変更されたかを一目で確認できます。

この新しいテストフレームワークは、go/doc の出力の正確性を保証するための強力なメカニズムを提供し、将来の変更に対する回帰テストを容易にします。

関連リンク

参考にした情報源リンク