[インデックス 16598] ファイルの概要
このコミットは、Go言語のテストツール cmd/go
におけるカバレッジ計測機能の改善に関するものです。具体的には、カバレッジデータをファイルに出力する機能と、テスト実行後にカバレッジの統計情報をパーセンテージで表示する機能が追加されています。また、カバレッジデータのダンプ処理が testing
パッケージに移動され、ファイルI/Oへのアクセスが容易になっています。
コミット
commit 8e8b8b85c261d219ed3b37638b6cd96d47da79d6
Author: Rob Pike <r@golang.org>
Date: Tue Jun 18 14:18:25 2013 -0700
cmd/go: write coverage to file, add percentage statistic
Move the data dumper to the testing package, where it has access
to file I/O.
Print a percentage value at the end of the run.
R=rsc, adg
CC=golang-dev
https://golang.org/cl/10264045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8e8b8b85c261d219ed3b37638b6cd96d47da79d6
元コミット内容
cmd/go: write coverage to file, add percentage statistic
Move the data dumper to the testing package, where it has access
to file I/O.
Print a percentage value at the end of the run.
このコミットの目的は、Goのテストカバレッジ機能を拡張し、カバレッジデータを指定されたファイルに書き出す機能と、テスト実行終了時にカバレッジのパーセンテージ統計を表示する機能を追加することです。また、カバレッジデータのダンプ処理を testing
パッケージに移管することで、ファイルI/Oへのアクセスを容易にしています。
変更の背景
Go言語の初期のテストカバレッジ機能は、カバレッジデータを標準出力に直接出力する形式でした。これは、自動化されたCI/CD環境や、カバレッジレポートツールとの連携において不便でした。カバレッジデータをファイルに保存できる機能は、これらの外部ツールとの統合を容易にし、より柔軟なカバレッジ分析を可能にします。
また、テスト実行後にカバレッジのパーセンテージを直接表示することで、開発者はテストの網羅率を即座に把握できるようになり、開発ワークフローの効率化に貢献します。
この変更は、Goのテストツールがより実用的なものになるための重要なステップでした。特に、カバレッジデータの処理ロジックを testing
パッケージに移動することで、cmd/go
と testing
パッケージ間の責務を明確にし、コードの保守性と再利用性を向上させています。
前提知識の解説
Go言語のテストとカバレッジ
Go言語には、標準ライブラリとして強力なテストフレームワーク testing
パッケージが組み込まれています。go test
コマンドを使用することで、Goのコードに対する単体テスト、ベンチマークテスト、およびカバレッジ計測を簡単に行うことができます。
- テスト関数:
func TestXxx(*testing.T)
の形式で定義され、テストの実行と結果の検証を行います。 - ベンチマーク関数:
func BenchmarkXxx(*testing.B)
の形式で定義され、コードのパフォーマンスを計測します。 - カバレッジ計測:
go test -cover
オプションを使用することで、テストがコードのどの部分を実行したかを計測できます。これにより、テストの網羅率を把握し、テストが不足している箇所を特定するのに役立ちます。
カバレッジのモード (-cover
フラグ)
go test -cover
には、以下のモードがあります。
set
: 各ステートメントが実行されたかどうか(真偽値)を記録します。count
: 各ステートメントが何回実行されたかを整数値で記録します。atomic
:count
と同様ですが、マルチスレッド環境でのテストにおいて正確なカウントを保証します。ただし、パフォーマンスオーバーヘッドが大きくなります。
カバレッジプロファイル (-coverprofile
フラグ)
このコミットで追加された -coverprofile
フラグは、カバレッジ計測の結果を特定のファイルに保存するためのものです。このファイルは、通常 cover.out
のような名前で保存され、後から go tool cover
コマンドで解析したり、HTMLレポートを生成したりするために使用されます。
go tool cover
go tool cover
は、カバレッジプロファイルファイルを解析し、様々な形式でカバレッジレポートを生成するためのツールです。例えば、go tool cover -html=cover.out
と実行することで、カバレッジ情報をハイライト表示したHTML形式のソースコードレポートを生成できます。
cmd/go
と testing
パッケージの役割
cmd/go
: Goコマンドラインツールの中核であり、ビルド、テスト、実行など、Goプロジェクトの管理に関する様々な機能を提供します。テストに関しては、go test
コマンドのフロントエンドとして機能し、テストの実行環境を準備し、testing
パッケージと連携して結果を処理します。testing
パッケージ: Go言語のテストフレームワークそのものです。テスト関数やベンチマーク関数の定義、テストの実行ロジック、カバレッジデータの収集と処理など、テストに関する低レベルな機能を提供します。
このコミットでは、カバレッジデータの「ダンプ(出力)」という具体的な処理を cmd/go
から testing
パッケージに移管することで、testing
パッケージがカバレッジデータの収集から出力までを一貫して管理できるようになり、よりクリーンなアーキテクチャになっています。
技術的詳細
このコミットの主要な変更点は以下の通りです。
-
testing
パッケージへのカバレッジデータダンプ機能の移動:- 以前は
src/cmd/go/test.go
内に存在したカバレッジデータの出力ロジック(coverDump
関数など)が削除されました。 - 新しく
src/pkg/testing/cover.go
ファイルが作成され、カバレッジデータの構造体 (CoverBlock
)、カバレッジカウンターとブロックを登録する関数 (RegisterCover
)、およびカバレッジレポートを生成しファイルに書き出す関数 (coverReport
) が定義されました。 testing.go
にcover
とcoverProfile
という新しいフラグが追加され、go test
コマンドからカバレッジモードと出力ファイルパスを受け取れるようになりました。testing.Main
関数が実行された後(つまり、すべてのテストが完了した後)にcoverReport()
が呼び出されるようにtesting.go
のafter()
関数が変更されました。
- 以前は
-
カバレッジプロファイル出力機能の追加:
cmd/go/test.go
に-coverprofile
フラグが追加され、カバレッジデータを指定されたファイルに書き出すことができるようになりました。src/pkg/testing/cover.go
のcoverReport
関数内で、*coverProfile
フラグが設定されている場合にファイルを作成し、カバレッジデータをそのファイルに書き込むロジックが実装されました。出力フォーマットは、各ブロックのファイル名、行番号、列番号、ステートメント数、および実行回数を含む形式です。
-
カバレッジパーセンテージ統計の表示:
src/pkg/testing/cover.go
のcoverReport
関数内で、実行されたステートメントの総数と、少なくとも1回実行されたステートメントの総数を計算し、その比率をパーセンテージとして標準出力に表示するロジックが追加されました。- パッケージ名も表示されるようになり、どのパッケージのカバレッジであるかが明確になりました。
-
CoverVar
のファイルパスの変更:cmd/go/test.go
のdeclareCoverVars
関数において、CoverVar
のFile
フィールドが単なるファイル名からfilepath.Join(importPath, file)
に変更されました。これにより、カバレッジデータがより完全なパス情報を持つようになり、複数のパッケージにまたがるカバレッジレポートの生成時にファイル名の衝突を避けることができます。
-
testflag.go
の更新:cmd/go/testflag.go
に-cover
と-coverprofile
フラグがpassToTest: true
として追加され、これらのフラグがgo test
コマンドからテストバイナリに正しく渡されるようになりました。-cover
フラグが指定された場合、自動的に-v
(verbose) モードが有効になるように変更されました(ただし、コメントで「TODO: This will change.」とあり、将来的に変更される可能性が示唆されています)。
これらの変更により、Goのカバレッジ計測機能は、より柔軟で使いやすくなり、外部ツールとの連携も容易になりました。
コアとなるコードの変更箇所
src/cmd/go/test.go
declareCoverVars
関数がimportPath
引数を受け取るようになり、CoverVar.File
の設定がfilepath.Join(importPath, file)
に変更されました。- カバレッジ関連のTODOコメントが削除されました。
-coverprofile
フラグに関する説明が追加されました。writeTestmain
関数内で生成されるテストメインファイルの内容が変更され、coverBlock
構造体がtesting.CoverBlock
に置き換えられ、coverRegisterFile
の引数も変更されました。coverDump()
の呼び出しが削除され、代わりにtesting.RegisterCover(coverCounters, coverBlocks)
が呼び出されるようになりました。
src/cmd/go/testflag.go
usageMessage
に-cover
と-coverprofile
の説明が追加されました。testFlagDefn
に-cover
と-coverprofile
がpassToTest: true
として追加されました。-cover
フラグが指定された場合にtestV = true
(verboseモードを有効にする) ロジックが追加されました。
src/pkg/testing/cover.go
(新規ファイル)
CoverBlock
構造体が定義されました。coverCounters
とcoverBlocks
というグローバル変数が定義されました。RegisterCover
関数が定義され、cmd/go
からカバレッジデータを受け取る役割を担います。mustBeNil
ヘルパー関数が定義されました。coverReport
関数が定義され、カバレッジプロファイルのファイルへの書き出しと、カバレッジパーセンテージの計算・表示を行います。
src/pkg/testing/testing.go
cover
とcoverProfile
という新しいフラグが定義されました。after()
関数内で、*cover != ""
の場合にcoverReport()
が呼び出されるようになりました。
コアとなるコードの解説
src/cmd/go/test.go
の変更
// declareCoverVars attaches the required cover variables names
// to the files, to be used when annotating the files.
func declareCoverVars(importPath string, files ...string) map[string]*CoverVar {
coverVars := make(map[string]*CoverVar)
for _, file := range files {
coverVars[file] = &CoverVar{
File: filepath.Join(importPath, file), // ここが変更点
Var: fmt.Sprintf("GoCover_%d", coverIndex),
}
coverIndex++
}
return coverVars
}
declareCoverVars
関数は、カバレッジ計測のために各ソースファイルに紐付けられる変数名を宣言します。この変更により、CoverVar
構造体の File
フィールドに、単なるファイル名ではなく、パッケージのインポートパスとファイル名を結合した完全なパスが設定されるようになりました。これにより、異なるパッケージに同じ名前のファイルが存在する場合でも、カバレッジデータが正しく識別されるようになります。
// writeTestmain 関数内で生成されるテストメインファイルの一部
{{if .CoverEnabled}}
// Only updated by init functions, so no need for atomicity.
var (
coverCounters = make(map[string][]uint32)
coverBlocks = make(map[string][]testing.CoverBlock) // testing.CoverBlock を使用
)
func init() {
{{range $file, $cover := .CoverVars}}
coverRegisterFile({{printf "%q" $cover.File}}, _test.{{$cover.Var}}.Count[:], _test.{{$cover.Var}}.Pos[:], _test.{{$cover.Var}}.NumStmt[:]) // 引数変更
{{end}}
}
func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts []uint16) { // 引数変更
if 3*len(counter) != len(pos) || len(counter) != len(numStmts) { // 条件追加
panic("coverage: mismatched sizes")
}
if coverCounters[fileName] != nil {
panic("coverage: duplicate counter array for " + fileName)
}
coverCounters[fileName] = counter
block := make([]testing.CoverBlock, len(counter)) // testing.CoverBlock を使用
for i := range counter {
block[i] = testing.CoverBlock{ // testing.CoverBlock を使用
Line0: pos[3*i+0],
Col0: uint16(pos[3*i+2]),
Line1: pos[3*i+1],
Col1: uint16(pos[3*i+2]>>16),
Stmts: numStmts[i], // Stmts フィールド追加
}
}
coverBlocks[fileName] = block
}
// coverDump() 関数は削除され、代わりに testing.RegisterCover が呼び出される
func main() {
testing.Main(matchString, tests, benchmarks, examples)
{{if .CoverEnabled}}
testing.RegisterCover(coverCounters, coverBlocks) // ここが変更点
{{end}}
testing.Main(matchString, tests, benchmarks, examples)
}
テストメインファイル生成ロジックの変更は、カバレッジデータの構造と登録方法の変更を反映しています。特に、coverBlock
が testing.CoverBlock
に置き換えられ、coverRegisterFile
関数が numStmts
(ステートメント数) を受け取るようになりました。これにより、各カバレッジブロックがカバーするステートメントの数を正確に記録できるようになります。
最も重要な変更は、main
関数内で coverDump()
の代わりに testing.RegisterCover(coverCounters, coverBlocks)
が呼び出されるようになった点です。これにより、カバレッジデータのダンプ処理が testing
パッケージに完全に委譲されました。
src/pkg/testing/cover.go
(新規ファイル)
// CoverBlock records the coverage data for a single basic block.
type CoverBlock struct {
Line0 uint32
Col0 uint16
Line1 uint32
Col1 uint16
Stmts uint16 // 追加されたフィールド
}
var (
coverCounters map[string][]uint32
coverBlocks map[string][]CoverBlock
)
// RegisterCover records the coverage data accumulators for the tests.
func RegisterCover(c map[string][]uint32, b map[string][]CoverBlock) {
coverCounters = c
coverBlocks = b
}
// coverReport reports the coverage percentage and writes a coverage profile if requested.
func coverReport() {
var f *os.File
var err error
if *coverProfile != "" { // -coverprofile フラグが設定されているかチェック
f, err = os.Create(toOutputDir(*coverProfile))
mustBeNil(err)
defer func() { mustBeNil(f.Close()) }()
}
var active, total int64
packageName := ""
for name, counts := range coverCounters {
// パッケージ名を抽出するロジック
if packageName == "" {
for i, c := range name {
if c == '/' {
packageName = name[:i]
}
}
}
blocks := coverBlocks[name]
for i, count := range counts {
stmts := int64(blocks[i].Stmts) // ステートメント数を取得
total += stmts
if count > 0 {
active += stmts // 実行されたステートメント数を加算
}
if f != nil { // ファイルへの書き出し
_, err := fmt.Fprintf(f, "%s:%d.%d,%d.%d %d %d\\n", name,
blocks[i].Line0, blocks[i].Col0,
blocks[i].Line1, blocks[i].Col1,
stmts, // ステートメント数も出力
count)
mustBeNil(err)
}
}
}
if total == 0 {
total = 1
}
if packageName == "" {
packageName = "package"
}
// カバレッジパーセンテージの表示
fmt.Printf("test coverage for %s: %.1f%% of statements\\n", packageName, 100*float64(active)/float64(total))
}
cover.go
は、カバレッジ計測の核心部分を担う新しいファイルです。
CoverBlock
構造体は、カバレッジ計測の対象となるコードブロックの開始・終了位置と、そのブロックに含まれるステートメント数を保持します。RegisterCover
関数は、cmd/go
から渡されたカバレッジカウンターとブロックのデータを内部変数に登録します。coverReport
関数は、このコミットの主要な機能を提供します。-coverprofile
フラグが指定されていれば、指定されたファイルにカバレッジデータを書き出します。出力フォーマットは、go tool cover
が解析できる形式です。- すべてのカバレッジブロックを走査し、実行されたステートメントの総数 (
active
) と、すべてのステートメントの総数 (total
) を計算します。 - 最終的に、
fmt.Printf
を使用して、計算されたカバレッジパーセンテージを標準出力に表示します。これにより、テスト実行後に即座にカバレッジ率を確認できるようになります。
src/pkg/testing/testing.go
の変更
var (
// ... 既存のフラグ ...
chatty = flag.Bool("test.v", false, "verbose: print additional output")
cover = flag.String("test.cover", "", "cover mode: set, count, atomic; default is none") // 新しいフラグ
coverProfile = flag.String("test.coverprofile", "", "write a coveraage profile to the named file after execution") // 新しいフラグ
// ... 既存のフラグ ...
)
func after() {
// ... 既存のプロファイル出力ロジック ...
if *cover != "" { // -cover フラグが設定されている場合
coverReport() // coverReport を呼び出す
}
}
testing.go
では、go test
コマンドが受け取る新しいフラグ (-test.cover
と -test.coverprofile
) が定義されました。
最も重要なのは after()
関数の変更です。after()
はすべてのテストが実行された後に呼び出されるフック関数です。この関数内で、-test.cover
フラグが設定されている場合に coverReport()
関数が呼び出されるようになりました。これにより、テスト実行の最後にカバレッジレポートが自動的に生成・表示されるようになります。
これらの変更により、Goのカバレッジ計測機能は、より統合され、使いやすく、そして外部ツールとの連携が容易なものへと進化しました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/
testing
パッケージのドキュメント: https://pkg.go.dev/testinggo test
コマンドのドキュメント: https://pkg.go.dev/cmd/go#hdr-Test_packagesgo tool cover
のドキュメント: https://pkg.go.dev/cmd/go#hdr-Go_tool_cover
参考にした情報源リンク
- Go言語の公式ドキュメントおよびパッケージドキュメント
- Go言語のソースコード (特に
src/cmd/go
とsrc/pkg/testing
ディレクトリ) - Go言語のコミット履歴
- Go言語のテストカバレッジに関する一般的な情報源