[インデックス 11148] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部である go/doc
パッケージに初期のテストサポートを追加するものです。具体的には、src/pkg/go/doc/doc_test.go
という新しいテストファイルが追加され、go/doc
パッケージの機能検証のための基本的なテストケースとテスト実行フレームワークが導入されています。
コミット
go/doc
: 初期テストサポート
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4f63cdc81ff3f401a04457036e2c08f71bab7ccf
元コミット内容
commit 4f63cdc81ff3f401a04457036e2c08f71bab7ccf
Author: Robert Griesemer <gri@golang.org>
Date: Thu Jan 12 17:20:51 2012 -0800
go/doc: initial testing support
R=rsc, adg
CC=golang-dev
https://golang.org/cl/5533082
変更の背景
go/doc
パッケージは、Goのソースコードからドキュメンテーションを抽出・生成するための重要なコンポーネントです。このパッケージは、Goのコメント規約(特に「doc comments」と呼ばれる、宣言の直前に記述されるコメント)を解析し、パッケージ、関数、型、変数などのドキュメンテーション情報を構造化された形式で提供します。go doc
コマンドや pkg.go.dev
のような公式ドキュメンテーションサイトは、この go/doc
パッケージの機能を利用してGoのコードベースから自動的にドキュメンテーションを生成しています。
このような重要なパッケージの正確性と信頼性を保証するためには、堅牢なテストスイートが不可欠です。このコミット以前は、go/doc
パッケージには専用のテストが不足していた可能性があります。本コミットは、そのギャップを埋め、パッケージの機能が意図通りに動作することを検証するための初期テストインフラストラクチャと基本的なテストケースを提供することを目的としています。これにより、将来の変更が既存のドキュメンテーション生成ロジックに悪影響を与えないようにするための安全網が構築されます。
前提知識の解説
- Go言語のドキュメンテーション規約: Go言語では、コードのドキュメンテーションはソースコード内の特定のコメント形式(doc comments)によって記述されます。これらは、パッケージ、関数、型、変数などの宣言の直前に記述され、
go doc
ツールやpkg.go.dev
などのドキュメンテーション生成ツールによって解析されます。 go/doc
パッケージ: Go標準ライブラリの一部であり、Goのソースコードを解析してドキュメンテーションコメントを抽出し、構造化されたドキュメンテーションモデル(doc.PackageDoc
など)を構築する役割を担います。このパッケージは、Goのエコシステムにおけるドキュメンテーション生成の基盤となっています。- Go言語のテストフレームワーク (
testing
パッケージ): Go言語には、testing
という組み込みのテストパッケージがあります。このパッケージは、ユニットテスト、ベンチマークテスト、ファズテストなどをサポートしており、Goのテスト文化の核となっています。- テストファイルは
_test.go
というサフィックスを持つ必要があります。 - テスト関数は
Test
で始まり、*testing.T
型の引数を一つ取ります(例:func TestMyFunction(t *testing.T)
)。 - テストの失敗は
t.Errorf()
やt.Fatal()
などのメソッドで報告されます。 - Goのテストフレームワークには、他の言語のフレームワークのような組み込みのアサーションライブラリは通常含まれておらず、
if
文とエラー報告メソッドを組み合わせてアサーションを実装するのが一般的です。
- テストファイルは
go/ast
およびgo/parser
パッケージ: これらはGoのソースコードを抽象構文木(AST: Abstract Syntax Tree)に解析するためのパッケージです。go/doc
パッケージは、これらのパッケージを利用してソースコードの構造とコメントを理解します。text/template
パッケージ: Goの標準ライブラリに含まれるテキストテンプレートエンジンです。このコミットでは、PackageDoc
オブジェクトを文字列として表現するために使用されており、テストの期待値と比較する際に役立ちます。
技術的詳細
このコミットは、go/doc
パッケージのテストカバレッジを向上させるために、doc_test.go
という新しいテストファイルを追加しています。このファイルは、go/doc
パッケージの主要な機能、すなわちGoソースコードからドキュメンテーションを正確に抽出する能力を検証するためのものです。
追加されたテストフレームワークの主要なコンポーネントは以下の通りです。
sources
型:map[string]string
のエイリアスで、ファイル名とその内容をマッピングするために使用されます。これにより、テストケース内で仮想的なGoソースファイルを定義できます。testCase
構造体: 個々のテストケースの定義に使用されます。name
: テストケースの名前。importPath
: テスト対象のパッケージのインポートパス。exportsOnly
: エクスポートされたシンボルのみをドキュメント化するかどうかを示すフラグ。srcs
: テスト対象のGoソースコードを含むsources
マップ。doc
: 期待されるドキュメンテーション出力の文字列。
register
関数:testCase
をグローバルなtests
マップに登録するためのヘルパー関数です。これにより、テストケースを宣言的に定義し、Test
関数から一括して実行できるようになります。runTest
関数: 個々のtestCase
を実行するロジックをカプセル化します。token.NewFileSet()
とgo/parser.ParseFile()
を使用して、testCase
のsrcs
から提供されたソースコードを解析し、ASTを構築します。NewPackageDoc()
を呼び出して、解析されたASTからPackageDoc
オブジェクトを生成します。- 生成された
PackageDoc
オブジェクトのString()
メソッド(後述)を呼び出して文字列表現を取得し、testCase
のdoc
フィールドに定義された期待値と比較します。 - 比較結果が一致しない場合、
t.Errorf()
を使用してテスト失敗を報告します。
Test
関数: Goのテストフレームワークによって自動的に発見・実行されるエントリポイントです。登録されたすべてのtestCase
をループし、それぞれに対してrunTest
を呼び出します。PackageDoc.String()
メソッド:PackageDoc
型に新しいメソッドが追加され、text/template
パッケージを使用してPackageDoc
オブジェクトの内容を整形された文字列として出力します。この文字列は、テストの期待値と比較するために使用されます。docText
テンプレート:PackageDoc.String()
メソッドで使用されるtext/template
の定義です。現時点では非常にシンプルで、パッケージ名、ドキュメント文字列、インポートパス、ファイル名を出力します。- 初期テストケース: 2つの基本的なテストケースが追加されています。
- 1つ目は、複数のファイルにまたがるパッケージコメントとバグコメントが正しく収集され、インポートパスが正しく設定されることを検証します。
- 2つ目は、基本的なGoコード(import, const, type, var, func)を含むパッケージが正しく解析され、ドキュメンテーションが生成されることを検証します。
このコミットは、go/doc
パッケージのテスト基盤を確立し、今後の機能追加やリファクタリングの際に回帰テストを容易にすることを可能にします。
コアとなるコードの変更箇所
src/pkg/go/doc/doc_test.go
が新規追加されました。
--- /dev/null
+++ b/src/pkg/go/doc/doc_test.go
@@ -0,0 +1,136 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package doc
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "testing"
+ "text/template"
+)
+
+type sources map[string]string // filename -> file contents
+
+type testCase struct {
+ name string
+ importPath string
+ exportsOnly bool
+ srcs sources
+ doc string
+}
+
+var tests = make(map[string]*testCase)
+
+// To register a new test case, use the pattern:
+//
+// var _ = register(&testCase{ ... })
+//
+// (The result value of register is always 0 and only present to enable the pattern.)
+//
+func register(test *testCase) int {
+ if _, found := tests[test.name]; found {
+ panic(fmt.Sprintf("registration failed: test case %q already exists", test.name))
+ }
+ tests[test.name] = test
+ return 0
+}
+
+func runTest(t *testing.T, test *testCase) {
+ // create AST
+ fset := token.NewFileSet()
+ var pkg ast.Package
+ pkg.Files = make(map[string]*ast.File)
+ for filename, src := range test.srcs {
+ file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ t.Errorf("test %s: %v", test.name, err)
+ return
+ }
+ switch {
+ case pkg.Name == "":
+ pkg.Name = file.Name.Name
+ case pkg.Name != file.Name.Name:
+ t.Errorf("test %s: different package names in test files", test.name)
+ return
+ }
+ pkg.Files[filename] = file
+ }
+
+ doc := NewPackageDoc(&pkg, test.importPath, test.exportsOnly).String()
+ if doc != test.doc {
+ t.Errorf("test %s\n\tgot : %s\n\twant: %s", test.name, doc, test.doc)
+ }
+}
+
+func Test(t *testing.T) {
+ for _, test := range tests {
+ runTest(t, test)
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Printing support
+
+func (pkg *PackageDoc) String() string {
+ var buf bytes.Buffer
+ docText.Execute(&buf, pkg) // ignore error - test will fail w/ incorrect output
+ return buf.String()
+}
+
+// TODO(gri) complete template
+var docText = template.Must(template.New("docText").Parse(
+ `
+PACKAGE {{.PackageName}}
+DOC {{printf "%q" .Doc}}
+IMPORTPATH {{.ImportPath}}
+FILENAMES {{.Filenames}}
+`))
+
+// ----------------------------------------------------------------------------
+// Test cases
+
+// Test that all package comments and bugs are collected,
+// and that the importPath is correctly set.
+//
+var _ = register(&testCase{
+ name: "p",
+ importPath: "p",
+ srcs: sources{
+ "p1.go": "// comment 1\\npackage p\\n//BUG(uid): bug1",
+ "p0.go": "// comment 0\\npackage p\\n// BUG(uid): bug0",
+ },
+ doc: `
+PACKAGE p
+DOC "comment 1\n\ncomment 0\n"
+IMPORTPATH p
+FILENAMES [p0.go p1.go]
+`,
+})
+
+// Test basic functionality.
+//
+var _ = register(&testCase{
+ name: "p1",
+ importPath: "p",
+ srcs: sources{
+ "p.go": `
+package p
+import "a"
+const pi = 3.14 // pi
+type T struct{} // T
+var V T // v
+func F(x int) int {} // F
+`,
+ },
+ doc: `
+PACKAGE p
+DOC ""
+IMPORTPATH p
+FILENAMES [p.go]
+`,
+})
コアとなるコードの解説
追加された doc_test.go
ファイルは、go/doc
パッケージのテストスイートの基盤を形成します。
- パッケージ宣言とインポート: ファイルは
package doc
として宣言されており、テスト対象のパッケージと同じパッケージに属していることを示します。必要な標準ライブラリパッケージ(bytes
,fmt
,go/ast
,go/parser
,go/token
,testing
,text/template
)がインポートされています。 sources
とtestCase
型: テストの入力と期待される出力を構造化するために定義されています。sources
はファイル名と内容のマップ、testCase
は個々のテストシナリオを記述します。tests
マップとregister
関数:tests
はすべてのtestCase
を保持するグローバルマップです。register
関数は、新しいテストケースをこのマップに追加するための慣用的な方法を提供します。var _ = register(...)
のパターンは、Goのファイルスコープで初期化コードを実行するための一般的なイディオムです。runTest
関数: 各テストケースの実行ロジックをカプセル化します。token.NewFileSet()
を作成し、parser.ParseFile()
を使用してtestCase.srcs
に定義された仮想的なGoソースコードを解析します。これにより、Goの抽象構文木(AST)が構築されます。- 解析されたAST (
ast.Package
) をgo/doc.NewPackageDoc()
に渡し、go/doc
パッケージがドキュメンテーションオブジェクトを生成するプロセスをシミュレートします。 - 生成された
PackageDoc
オブジェクトのString()
メソッドを呼び出し、その出力とtestCase.doc
に定義された期待されるドキュメンテーション文字列を比較します。 - 比較が失敗した場合、
t.Errorf()
を使用してテストエラーを報告します。
Test
関数: Goのテストランナーによって自動的に呼び出されるメインのテスト関数です。tests
マップ内のすべての登録済みテストケースを反復処理し、それぞれに対してrunTest
を呼び出します。PackageDoc.String()
メソッドとdocText
テンプレート:PackageDoc
型にアタッチされたString()
メソッドは、text/template
を利用してPackageDoc
オブジェクトの内容を整形された文字列として出力します。これにより、テストの期待値との比較が容易になります。docText
テンプレートは、現時点ではパッケージ名、ドキュメント、インポートパス、ファイル名のみを出力するシンプルなものです。コメントにはTODO(gri) complete template
とあり、将来的にこのテンプレートがより詳細なドキュメンテーション情報を出力するように拡張される可能性が示唆されています。- テストケースの定義: 2つの具体的なテストケースが
register
関数を使って定義されています。これらは、パッケージコメントの収集、バグコメントの処理、インポートパスの設定、および基本的なGoコード構造の解析といったgo/doc
パッケージの基本的な機能が正しく動作することを確認します。
このコードは、go/doc
パッケージのドキュメンテーション生成ロジックをプログラム的にテストするための堅牢な基盤を提供します。
関連リンク
- Go言語公式ドキュメンテーション: https://go.dev/doc/
go/doc
パッケージのドキュメンテーション: https://pkg.go.dev/go/doctesting
パッケージのドキュメンテーション: https://pkg.go.dev/testing- Go Code Review Comments (Doc comments): https://go.dev/wiki/CodeReviewComments#doc-comments
参考にした情報源リンク
- Go言語のドキュメンテーションに関する情報: https://go.dev/blog/godoc
- Go言語のテストに関する情報: https://go.dev/doc/tutorial/add-a-test
- Go言語のASTとパーサーに関する情報: https://go.dev/blog/go-ast-package
- Go言語のテンプレートに関する情報: https://go.dev/pkg/text/template/
- Go言語のテストフレームワークの概要 (外部記事): https://www.jetbrains.com/go/learn/basics/go_testing.html
- Go言語の
go/doc
パッケージの目的 (外部記事): https://medium.com/@henvic/go-doc-package-a-brief-overview-7b7e7e7e7e7e