統合テストのためのカバレージプロファイリングのサポート

目次

概要
カバレージプロファイリング用のバイナリを構築する
カバレージを計測したバイナリを実行する
カバレージデータファイルを使用する
よくある質問
リソース
用語集

Go 1.20 以降、Go はアプリケーションと統合テスト(Go プログラムのより大規模で複雑なテスト)からカバレージプロファイルの収集をサポートしています。

概要

Go は「go test -coverprofile=... <pkg_target>」コマンドを使用して、パッケージユニットテストのレベルでカバレージプロファイルを収集するための使いやすいサポートを提供します。Go 1.20 以降、ユーザーはより大規模な 統合テスト のカバレージプロファイルを収集できるようになりました。統合テストは、特定のアプリケーションバイナリを複数回実行する、より大規模で複雑なテストです。

ユニットテストでは、カバレージプロファイルの収集とレポートの生成には 2 つのステップが必要です。1 つ目は go test -coverprofile=... の実行、2 つ目はレポートを生成するための go tool cover {-func,-html} の呼び出しです。

統合テストでは、3 つのステップ、つまり ビルド ステップ、実行 ステップ(ビルドステップからバイナリを複数回呼び出す場合があります)、および最後に レポート ステップが必要です。以下で説明します。

カバレージプロファイリング用のバイナリを構築する

カバレージプロファイルの収集用にアプリケーションを構築するには、アプリケーションバイナリのターゲットでgo buildを呼び出すときに-coverフラグを渡します。go build -cover の呼び出し例については、以下のセクションを参照してください。結果のバイナリは、環境変数の設定を使用してカバレージプロファイルをキャプチャして実行できます(実行に関する次のセクションを参照してください)。

パッケージがインストルメンテーションにどのように選択されるか

特定の「go build -cover」呼び出しの間、Go コマンドはカバレージプロファイリング用にメインモジュール内のパッケージを選択します。ビルドに投入される他のパッケージ(go.mod にリストされている依存関係、または Go 標準ライブラリの一部であるパッケージ)は、デフォルトでは含まれません。

たとえば、メインパッケージ、ローカルのメインモジュールパッケージgreetings、および他のパッケージからインポートされた一連のパッケージ(特にrsc.io/quoteおよびfmt)を含むおもちゃのプログラムを示します(フルプログラムへのリンク)。

$ cat go.mod
module mydomain.com

go 1.20

require rsc.io/quote v1.5.2

require (
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
    rsc.io/sampler v1.3.0 // indirect
)

$ cat myprogram.go
package main

import (
    "fmt"
    "mydomain.com/greetings"
    "rsc.io/quote"
)

func main() {
    fmt.Printf("I say %q and %q\n", quote.Hello(), greetings.Goodbye())
}
$ cat greetings/greetings.go
package greetings

func Goodbye() string {
    return "see ya"
}
$ go build -cover -o myprogram.exe .
$

コマンドラインフラグ「-cover」でこのプログラムをビルドして実行すると、mainmydomain.com/greetingsの正確に2つのパッケージがプロファイルに含まれます。他の依存パッケージは除外されます。

カバレッジに含めるパッケージを細かく制御したいユーザーは、「-coverpkg」フラグでビルドできます。例

$ go build -cover -o myprogramMorePkgs.exe -coverpkg=io,mydomain.com,rsc.io/quote .
$

上記のビルドでは、mydomain.comのメインパッケージ、rsc.io/quoteパッケージ、およびioパッケージがプロファイリング用に選択されています。mydomain.com/greetingsは具体的にリストされていないため、メインモジュール内に存在する場合でも、プロファイルから除外されます。

カバレージを計測したバイナリを実行する

-cover」でビルドされたバイナリは、環境変数GOCOVERDIRで指定したディレクトリに実行の最後にプロファイルデータファイルを書き込みます。例

$ go build -cover -o myprogram.exe myprogram.go
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$

somedataディレクトリに書き込まれた2つのファイルに注意してください。これらの(バイナリ)ファイルにはカバレッジ結果が含まれています。これらのデータファイルから人間が判読できる結果を生成する方法については、レポートの次のセクションを参照してください。

GOCOVERDIR環境変数が設定されていない場合でも、カバレッジが計測されたバイナリは正しく実行されますが、警告が表示されます。例

$ ./myprogram.exe
warning: GOCOVERDIR not set, no coverage data emitted
I say "Hello, world." and "see ya"
$

複数のランを含むテスト

統合テストでは、多くの場合、複数のプログラム実行が含まれる可能性があります。プログラムが「-cover」でビルドされている場合、各実行は新しいデータファイルを生成します。例

$ mkdir somedata2
$ GOCOVERDIR=somedata2 ./myprogram.exe          // first run
I say "Hello, world." and "see ya"
$ GOCOVERDIR=somedata2 ./myprogram.exe -flag    // second run
I say "Hello, world." and "see ya"
$ ls somedata2
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456041.1670259309405583534
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456047.1670259309410891043
covmeta.890814fca98ac3a4d41b9bd2a7ec9f7f
$

カバレッジデータ出力ファイルには、2つの種類があります。メタデータファイル(ソースファイル名や関数名など、実行ごとに不変の項目を含む)と、カウンターデータファイル(実行されたプログラムの部分を記録する)です。

上記の例では、最初のランは2つのファイル(カウンターとメタ)を生成しましたが、2番目のランはカウンターデータファイルのみを生成しました。メタデータは実行ごとに変化しないため、一度だけ書き込めば済みます。

カバレージデータファイルを使用する

Go 1.20では、GOCOVERDIRディレクトリのカバレッジデータファイルの読み書きに使用できる新しいツール「covdata」が導入されています。

Goのcovdataツールは、さまざまなモードで実行できます。covdataツールの呼び出しの一般的な形式は次のようになります。

$ go tool covdata <mode> -i=<dir1,dir2,...> ...flags...

ここで、「-i」フラグは読み取るディレクトリのリストを提供します。各ディレクトリは(GOCOVERDIRを介して)カバレッジが計測されたバイナリの実行から派生しています。

カバレッジプロファイルレポートの作成

このセクションでは、go tool covdataを使用してカバレッジデータファイルから人間が判読できるレポートを作成する方法について説明します。

カバレッジされたステートメントのパーセントの報告

各計測パッケージの「カバレッジされたステートメントのパーセント」メトリックを報告するには、「go tool covdata percent -i=<directory>」コマンドを使用します。上記の実行セクションの例を使用します。

$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata
    main    coverage: 100.0% of statements
    mydomain.com/greetings  coverage: 100.0% of statements
$

ここで報告されている「ステートメントカバレッジ」のパーセンテージは、go test -coverで報告されるものに直接対応しています。

従来のテキスト形式への変換

covdata textfmtセレクターを使用して、バイナリカバレッジデータファイルを「go test -coverprofile=<outfile>」で生成される従来のテキスト形式に変換できます。生成されたテキストファイルは、追加のレポートを作成するために「go tool cover -func」または「go tool cover -html」とともに使用できます。例

$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata textfmt -i=somedata -o profile.txt
$ cat profile.txt
mode: set
mydomain.com/myprogram.go:10.13,12.2 1 1
mydomain.com/greetings/greetings.go:3.23,5.2 1 1
$ go tool cover -func=profile.txt
mydomain.com/greetings/greetings.go:3:  Goodbye     100.0%
mydomain.com/myprogram.go:10:       main        100.0%
total:                  (statements)    100.0%
$

マージ

「go tool covdata」のサブコマンドの「merge」は、複数のデータディレクトリからプロファイルをマージするために使用できます。

たとえば、macOSとWindowsの両方で実行されるプログラムを考えてみましょう。このプログラムの著者は、各オペレーティングシステムで別々に実行されたカバレッジプロファイルを1つのプロファイルコーパスに結合して、クロスプラットフォームのカバレッジサマリーを作成しようとする場合があります。たとえば

$ ls windows_datadir
covcounters.f3833f80c91d8229544b25a855285890.1025623.1667481441036838252
covcounters.f3833f80c91d8229544b25a855285890.1025628.1667481441042785007
covmeta.f3833f80c91d8229544b25a855285890
$ ls macos_datadir
covcounters.b245ad845b5068d116a4e25033b429fb.1025358.1667481440551734165
covcounters.b245ad845b5068d116a4e25033b429fb.1025364.1667481440557770197
covmeta.b245ad845b5068d116a4e25033b429fb
$ ls macos_datadir
$ mkdir merged
$ go tool covdata merge -i=windows_datadir,macos_datadir -o merged
$

上記のマージ操作は、指定された入力ディレクトリからのデータを結合し、マージされた一連のデータファイルを「merged」ディレクトリに書き込みます。

パッケージ選択

ほとんどの「go tool covdata」コマンドは、「-pkg」フラグをサポートして、操作の一部としてパッケージの選択を実行します。「-pkg」の引数は、Goコマンドの「-coverpkg」フラグで使用されるものと同じ形式を取ります。例


$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata -pkg=mydomain.com/greetings
    mydomain.com/greetings  coverage: 100.0% of statements
$ go tool covdata percent -i=somedata -pkg=nonexistentpackage
$

「-pkg」フラグは、特定のレポートで関心のある特定のパッケージのサブセットを選択するために使用できます。

よくある質問

  1. 私のgo.modファイルに記載されているインポートされたパッケージのすべてについて、カバレッジインストゥルメンテーションを要求するにはどうすればよいでしょうか。
  2. GOPATH/GO111MODULE=offモードでgo build -coverを使用できますか。
  3. 私のプログラムがパニックを起こした場合、カバレッジデータは書き込まれますか。
  4. -coverpkg=mainは、プロファイリングの対象としてメインパッケージを選択しますか。

私のgo.modファイルに記載されているインポートされたパッケージのすべてについて、カバレッジインストゥルメンテーションを要求するにはどうすればよいでしょうか。

デフォルトでは、go build -coverはすべてのメインモジュールパッケージに対してカバレッジをインストゥルメントしますが、メインモジュール外のインポート(たとえば、標準ライブラリパッケージやgo.modに記載されているインポート)に対してはインストゥルメントしません。すべての非stdlib依存関係に対するインストゥルメンテーションを要求する1つの方法は、go listの出力を-coverpkgにフィードすることです。例を次に示します。先ほど引用したサンプルプログラムを使用します。

$ go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' -deps . | paste -sd "," > pkgs.txt
$ go build -o myprogram.exe -coverpkg=`cat pkgs.txt` .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
$ go tool covdata percent -i=somedata
    golang.org/x/text/internal/tag  coverage: 78.4% of statements
    golang.org/x/text/language  coverage: 35.5% of statements
    mydomain.com    coverage: 100.0% of statements
    mydomain.com/greetings  coverage: 100.0% of statements
    rsc.io/quote    coverage: 25.0% of statements
    rsc.io/sampler  coverage: 86.7% of statements
$

GO111MODULE=offモードでgo build -coverを使用できますか。

はい、go build -coverはGO111MODULE=offで機能します。GO111MODULE=offモードでプログラムをビルドすると、コマンドラインでターゲットとして具体的に指定されたパッケージのみがプロファイリング対象としてインストゥルメントされます。-coverpkgフラグを使用して、プロファイルに追加のパッケージを含めます。

私のプログラムがパニックを起こした場合、カバレッジデータは書き込まれますか。

go build -coverを使用してビルドされたプログラムは、プログラムがos.Exit()を実行するか、main.mainから正常に戻る場合にのみ、実行の終わりに完全なプロファイルデータを出力します。プログラムが回復不能なパニックで終了した場合、またはプログラムが致命的な例外(セグメンテーション違反、ゼロ除算など)が発生した場合、実行中に実行されたステートメントからのプロファイルデータは失われます。

-coverpkg=mainは、プロファイリングの対象としてメインパッケージを選択しますか。

-coverpkgフラグはインポートパスのリストを受け取り、パッケージ名のリストを受け取りません。メインパッケージをカバレッジインストゥルメンテーションの対象として選択する場合は、名前ではなくインポートパスで識別してください。例(このサンプルプログラムを使用)

$ go list -m
mydomain.com
$ go build -coverpkg=main -o oops.exe .
warning: no packages being built depend on matches for pattern main
$ go build -coverpkg=mydomain.com -o myprogram.exe .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ go tool covdata percent -i=somedata
    mydomain.com    coverage: 100.0% of statements
$

リソース

用語集

ユニットテスト: Goのtestingパッケージを利用し、特定のGoパッケージに関連付けられた*_test.goファイル内のテスト。

インテグレーションテスト: 特定のアプリケーションまたはバイナリに対する、より包括的で負荷の大きいテスト。インテグレーションテストには通常、プログラムまたは一連のプログラムの構築が含まれます。その後、Goのtestingパッケージに基づく場合とそうでない場合があるテストハーネスの制御下で、複数の入力とシナリオを使用してプログラムを連続的に実行します。