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

[インデックス 16759] ファイルの概要

このコミットは、Go言語のテストツール (go test) と testing パッケージにおける、カバレッジ計測の挙動を合理化し、ユーザーエクスペリエンスを向上させるための変更です。特に、go test -c でビルドされたテストバイナリを直接実行する際の操作が簡素化され、カバレッジモードがビルド時に固定されるという性質がより明確に反映されています。

コミット

commit ccc4553491ea4df4b5b3489811359eadb24899bf
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jul 12 20:40:30 2013 -0400

    cmd/go, testing: streamline direct use of test binaries
    
    Before:
    
            $ go test -c -cover fmt
            $ ./fmt.test -test.covermode=set
            PASS
            coverage: 65.1% of statements in strconv
            $
    
    After:
    
            $ go test -c -cover fmt
            $ ./fmt.test
            PASS
            coverage: 65.1% of statements in strconv
            $
    
    In addition to being cumbersome, the old flag didn't make sense:
    the cover mode cannot be changed after the binary has been built.
    
    Another useful effect of this CL is that if you happen to do
    
            $ go test -c -covermode=atomic fmt
    
    and then forget you did that and run benchmarks,
    the final line of the output (the coverage summary) reminds you
    that you are benchmarking with coverage enabled, which might
    not be what you want.
    
            $ ./fmt.test -test.bench .
            PASS
            BenchmarkSprintfEmpty   10000000               217 ns/op
            BenchmarkSprintfString   2000000               755 ns/op
            BenchmarkSprintfInt      2000000               774 ns/op
            BenchmarkSprintfIntInt   1000000              1363 ns/op
            BenchmarkSprintfPrefixedInt      1000000              1501 ns/op
            BenchmarkSprintfFloat    1000000              1257 ns/op
            BenchmarkManyArgs         500000              5346 ns/op
            BenchmarkScanInts           1000           2562402 ns/op
            BenchmarkScanRecursiveInt            500           3189457 ns/op
            coverage: 91.4% of statements
            $
    
    As part of passing the new mode setting in via _testmain.go, merge
    the two registration mechanisms into one extensible mechanism
    (a struct).
    
    R=r
    CC=golang-dev
    https://golang.org/cl/11219043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/ccc4553491ea4df4b5b3489811359eadb24899bf

元コミット内容

このコミットは、Goのテストバイナリを直接実行する際のカバレッジ計測の挙動を改善します。以前は、go test -c -cover でビルドされたテストバイナリを実行する際に、カバレッジモードを -test.covermode=set のように明示的に指定する必要がありました。この変更により、ビルド時に指定されたカバレッジモードがテストバイナリに埋め込まれるため、実行時に -test.covermode フラグを指定する必要がなくなりました。

また、この変更は、カバレッジが有効な状態でベンチマークを実行した場合に、その旨をユーザーに通知する機能も追加します。これにより、カバレッジ計測がベンチマーク結果に影響を与える可能性があることをユーザーが認識できるようになります。

内部的には、カバレッジモードの設定を _testmain.go (テスト実行のためにGoツールが生成するメインファイル) を介して渡すように変更し、既存の2つのカバレッジ登録メカニズムを1つの拡張可能な構造体 (testing.Cover) に統合しています。

変更の背景

この変更の背景には、主に以下の2つの課題がありました。

  1. 冗長性と誤解: 以前の go test -c -cover でビルドされたテストバイナリを直接実行する際、-test.covermode=set のようにカバレッジモードを明示的に指定する必要がありました。しかし、カバレッジ計測のためのコードはバイナリのビルド時に既に挿入されており、そのモード(set, count, atomic)はビルド時に決定されます。実行時にこのフラグを渡すことは冗長であるだけでなく、「実行時にカバレッジモードを変更できる」という誤解を与える可能性がありました。実際には、ビルドされたバイナリのカバレッジモードは変更できません。
  2. ユーザーへの情報提供の不足: カバレッジ計測は、プログラムの実行速度に影響を与える可能性があります。特にベンチマークを実行する際にカバレッジが有効になっていると、ベンチマーク結果が不正確になる可能性があります。以前は、カバレッジが有効な状態でベンチマークを実行しても、その旨が明示的に通知されなかったため、ユーザーが意図せずカバレッジ計測の影響を受けたベンチマーク結果を見てしまう可能性がありました。

このコミットは、これらの課題を解決し、Goのテストおよびカバレッジ計測のユーザーエクスペリエンスと堅牢性を向上させることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のテストおよびカバレッジ計測に関する基本的な知識が必要です。

  • go test コマンド: Go言語の標準テストツールです。テストの実行、ベンチマークの実行、カバレッジ計測などを行います。
  • go test -c: テストバイナリをコンパイルしますが、実行はしません。これにより、テストバイナリを後で手動で実行したり、特定の環境で実行したりすることが可能になります。
  • go test -cover: カバレッジ計測を有効にします。これにより、テスト実行中にどのコードが実行されたかを追跡し、カバレッジ率を報告できます。
  • go test -covermode: カバレッジ計測のモードを指定します。
    • set: 各ステートメントが実行されたかどうかのみを記録します(デフォルト)。
    • count: 各ステートメントが何回実行されたかを記録します。
    • atomic: count と同様ですが、マルチスレッド環境での正確性を保証するためにアトミック操作を使用します。count よりもコストが高くなります。
  • go test -coverprofile <file>: カバレッジプロファイルを指定されたファイルに書き出します。このプロファイルは、go tool cover コマンドで解析し、HTMLレポートなどを生成できます。
  • _testmain.go: go test コマンドがテストを実行するために内部的に生成するGoソースファイルです。このファイルには、テストの初期化、テスト関数の登録、テストの実行ロジックなどが含まれます。ユーザーが直接編集することは通常ありませんが、Goのテストインフラストラクチャの重要な部分です。
  • testing パッケージ: Goの標準ライブラリの一部で、テスト、ベンチマーク、カバレッジ計測のためのAPIを提供します。testing.T (テスト関数)、testing.B (ベンチマーク関数) などが含まれます。
  • カバレッジ計測の仕組み: Goのカバレッジ計測は、コンパイル時にソースコードに計測用のコード(インストゥルメンテーション)を挿入することで実現されます。これにより、プログラムの実行中に各ステートメントの実行状況を追跡できます。このインストゥルメンテーションはビルド時に行われるため、一度ビルドされたバイナリのカバレッジ計測の挙動(モードなど)は変更できません。

技術的詳細

このコミットの技術的な詳細は、主に以下の点に集約されます。

  1. カバレッジモードの埋め込み:

    • 以前は、go test -c -cover でビルドされたテストバイナリを実行する際に、ユーザーが -test.covermode フラグを明示的に指定する必要がありました。これは、go コマンドがテストバイナリを生成する際に、カバレッジモードの情報をバイナリに直接埋め込んでいなかったためです。
    • この変更により、go test コマンドは、テストバイナリをビルドする際に、指定されたカバレッジモード(またはデフォルトの set モード)を _testmain.go を介して testing パッケージに渡すようになりました。
    • 具体的には、src/cmd/go/test.go 内の main 関数テンプレートが変更され、testing.RegisterCover 関数に渡される引数が、個別の変数から新しい testing.Cover 構造体のインスタンスに変わりました。この構造体には Mode フィールドが含まれており、ビルド時に決定されたカバレッジモードがここに設定されます。
  2. testing.Cover 構造体の導入と登録メカニズムの統合:

    • src/pkg/testing/cover.go に新しい type Cover struct が導入されました。この構造体は、カバレッジ計測に必要なすべての情報を一元的に管理します。
      type Cover struct {
          Mode            string
          Counters        map[string][]uint32
          Blocks          map[string][]CoverBlock
          CoveredPackages string
      }
      
    • 以前は、coverCounterscoverBlockstestedPackagecoveredPackage といったグローバル変数が個別に存在し、testing.RegisterCovertesting.CoveredPackage という2つの異なる関数で登録されていました。
    • この変更により、これらのグローバル変数は削除され、すべての情報が testing.Cover 構造体にカプセル化されました。そして、testing.RegisterCover 関数は、この testing.Cover 構造体のインスタンスを引数として受け取るように変更されました。これにより、カバレッジ情報の登録メカニズムが単一の、より拡張性の高いインターフェースに統合されました。
  3. ベンチマーク実行時のカバレッジ通知:

    • src/pkg/testing/testing.goafter() 関数(テスト実行後に呼び出される)内で、cover.Mode が設定されている場合(つまり、カバレッジが有効な場合)に coverReport() が呼び出されるようになりました。
    • これにより、たとえベンチマークのみを実行した場合でも、カバレッジが有効であれば最後にカバレッジのサマリーが出力され、ユーザーにカバレッジ計測がアクティブであることを通知します。これは、カバレッジ計測がベンチマークのパフォーマンスに影響を与える可能性があるため、ユーザーへの重要なフィードバックとなります。
  4. -test.coverprofile とカバレッジ無効バイナリのチェック:

    • src/pkg/testing/testing.gobefore() 関数(テスト実行前に呼び出される)に新しいチェックが追加されました。
    • もしユーザーが -test.coverprofile フラグを指定しているにもかかわらず、テストバイナリがカバレッジ計測を有効にしてビルドされていない場合(cover.Mode が空の場合)、エラーメッセージを出力して終了するようになりました。これにより、無効な操作に対するより明確なフィードバックが提供されます。

これらの変更により、Goのカバレッジ計測はより直感的で堅牢になり、ユーザーが意図しない挙動に遭遇する可能性が低減されました。

コアとなるコードの変更箇所

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/cmd/go/doc.go: go test のドキュメントにおける -covermode, -coverpkg, -coverprofile フラグの説明が「Implies -cover.」から「Sets -cover.」に変更され、これらのフラグがカバレッジを有効にするという意図が明確化されました。また、-cover に関するTODOコメントが削除されました。
  • src/cmd/go/test.go:
    • testFuncs 構造体に CoverMode() メソッドが追加されました。
    • 最も重要な変更は、テストバイナリの main 関数を生成するテンプレート部分です。testing.CoveredPackagetesting.RegisterCover の呼び出しが統合され、testing.Cover 構造体のインスタンスを渡す形式に変更されました。
      --- a/src/cmd/go/test.go
      +++ b/src/cmd/go/test.go
      @@ -1201,8 +1205,12 @@ func coverRegisterFile(fileName string, counter []uint32, pos []uint32, numStmts
       
       func main() {
       {{if .CoverEnabled}}\n-\ttesting.CoveredPackage({{printf "%q" .Tested}}, {{printf "%q" .Covered}})\n-\ttesting.RegisterCover(coverCounters, coverBlocks)\n+\ttesting.RegisterCover(testing.Cover{\n+\t\tMode: {{printf "%q" .CoverMode}},\n+\t\tCounters: coverCounters,\n+\t\tBlocks: coverBlocks,\n+\t\tCoveredPackages: {{printf "%q" .Covered}},\n+\t})\n {{end}}\n         	testing.Main(matchString, tests, benchmarks, examples)\n         }
      
  • src/cmd/go/testflag.go:
    • -covermode フラグのヘルプメッセージが「passes -test.covermode to test if -cover」から「specifies mode for coverage analysis」に変更されました。
    • testFlags 関数内で、testCoverMode がデフォルトで "set" に初期化されるようになりました。
    • 以前、testCover が有効な場合に -test.covermodepassToTest に追加していたロジックが削除されました。これは、モードがバイナリに直接埋め込まれるようになったためです。
  • src/pkg/testing/cover.go:
    • coverCounters, coverBlocks, testedPackage, coveredPackage といったグローバル変数が削除されました。
    • type Cover struct が新しく定義され、カバレッジ関連のすべての情報(モード、カウンター、ブロック、対象パッケージ)をカプセル化するようになりました。
    • RegisterCover 関数のシグネチャが変更され、新しい Cover 構造体を受け取るようになりました。
    • coverReport 関数内のカバレッジデータへのアクセスが、新しい cover グローバル変数(Cover 型)を介して行われるように変更されました。
  • src/pkg/testing/testing.go:
    • coverMode フラグの定義が削除されました。
    • before() 関数に、-test.coverprofile が指定されているにもかかわらずカバレッジが有効でない場合にエラーを出力するロジックが追加されました。
    • after() 関数内で、カバレッジレポートの生成条件が *coverMode != "" から cover.Mode != "" に変更されました。

コアとなるコードの解説

このコミットの核心は、カバレッジ計測の「モード」が、テストバイナリのビルド時に決定され、そのバイナリ自体に埋め込まれるようになった点です。これにより、実行時に冗長なフラグ指定が不要になり、カバレッジ計測の性質がより明確になりました。

具体的には、src/cmd/go/test.go のテンプレートで生成される _testmain.gomain 関数が変更され、testing.RegisterCover 関数に渡す情報が、以前のバラバラな引数から、新しく定義された testing.Cover 構造体のインスタンスに集約されました。この testing.Cover 構造体(src/pkg/testing/cover.go で定義)は、カバレッジモード (Mode)、計測カウンター (Counters)、コードブロック情報 (Blocks)、そしてカバレッジ対象パッケージ (CoveredPackages) を一元的に保持します。

// src/cmd/go/test.go の main 関数テンプレートの一部
func main() {
{{if .CoverEnabled}} // カバレッジが有効な場合
	testing.RegisterCover(testing.Cover{ // 新しい Cover 構造体を登録
		Mode: {{printf "%q" .CoverMode}}, // ビルド時に決定されたカバレッジモード
		Counters: coverCounters,
		Blocks: coverBlocks,
		CoveredPackages: {{printf "%q" .Covered}},
	})
{{end}}
	testing.Main(matchString, tests, benchmarks, examples)
}

この変更により、テストバイナリは自身のカバレッジモードを「知っている」ため、実行時にユーザーが -test.covermode を指定する必要がなくなりました。また、src/pkg/testing/testing.go では、この埋め込まれた cover.Mode を参照して、カバレッジレポートの生成や、無効な -test.coverprofile の使用に対するエラーチェックを行うようになりました。

この統合されたアプローチは、Goのテストインフラストラクチャの内部構造を簡素化し、将来的な拡張性も高めています。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (上記に記載)
  • Go言語のソースコード (上記に記載のファイルパス)
  • Go言語の公式ドキュメントおよびブログ記事 (上記「関連リンク」に記載)
  • Go言語のテストとカバレッジに関する一般的な知識