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

[インデックス 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ソースコードからドキュメンテーションを正確に抽出する能力を検証するためのものです。

追加されたテストフレームワークの主要なコンポーネントは以下の通りです。

  1. sources: map[string]string のエイリアスで、ファイル名とその内容をマッピングするために使用されます。これにより、テストケース内で仮想的なGoソースファイルを定義できます。
  2. testCase 構造体: 個々のテストケースの定義に使用されます。
    • name: テストケースの名前。
    • importPath: テスト対象のパッケージのインポートパス。
    • exportsOnly: エクスポートされたシンボルのみをドキュメント化するかどうかを示すフラグ。
    • srcs: テスト対象のGoソースコードを含む sources マップ。
    • doc: 期待されるドキュメンテーション出力の文字列。
  3. register 関数: testCase をグローバルな tests マップに登録するためのヘルパー関数です。これにより、テストケースを宣言的に定義し、Test 関数から一括して実行できるようになります。
  4. runTest 関数: 個々の testCase を実行するロジックをカプセル化します。
    • token.NewFileSet()go/parser.ParseFile() を使用して、testCasesrcs から提供されたソースコードを解析し、ASTを構築します。
    • NewPackageDoc() を呼び出して、解析されたASTから PackageDoc オブジェクトを生成します。
    • 生成された PackageDoc オブジェクトの String() メソッド(後述)を呼び出して文字列表現を取得し、testCasedoc フィールドに定義された期待値と比較します。
    • 比較結果が一致しない場合、t.Errorf() を使用してテスト失敗を報告します。
  5. Test 関数: Goのテストフレームワークによって自動的に発見・実行されるエントリポイントです。登録されたすべての testCase をループし、それぞれに対して runTest を呼び出します。
  6. PackageDoc.String() メソッド: PackageDoc 型に新しいメソッドが追加され、text/template パッケージを使用して PackageDoc オブジェクトの内容を整形された文字列として出力します。この文字列は、テストの期待値と比較するために使用されます。
  7. docText テンプレート: PackageDoc.String() メソッドで使用される text/template の定義です。現時点では非常にシンプルで、パッケージ名、ドキュメント文字列、インポートパス、ファイル名を出力します。
  8. 初期テストケース: 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)がインポートされています。
  • sourcestestCase: テストの入力と期待される出力を構造化するために定義されています。sources はファイル名と内容のマップ、testCase は個々のテストシナリオを記述します。
  • tests マップと register 関数: tests はすべての testCase を保持するグローバルマップです。register 関数は、新しいテストケースをこのマップに追加するための慣用的な方法を提供します。var _ = register(...) のパターンは、Goのファイルスコープで初期化コードを実行するための一般的なイディオムです。
  • runTest 関数: 各テストケースの実行ロジックをカプセル化します。
    1. token.NewFileSet() を作成し、parser.ParseFile() を使用して testCase.srcs に定義された仮想的なGoソースコードを解析します。これにより、Goの抽象構文木(AST)が構築されます。
    2. 解析されたAST (ast.Package) を go/doc.NewPackageDoc() に渡し、go/doc パッケージがドキュメンテーションオブジェクトを生成するプロセスをシミュレートします。
    3. 生成された PackageDoc オブジェクトの String() メソッドを呼び出し、その出力と testCase.doc に定義された期待されるドキュメンテーション文字列を比較します。
    4. 比較が失敗した場合、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 パッケージのドキュメンテーション生成ロジックをプログラム的にテストするための堅牢な基盤を提供します。

関連リンク

参考にした情報源リンク