> Source: https://www.ymotongpoo.com/works/goblog-ja/integration-test-coverage/


# Goの統合テストにおけるコードカバレッジ

[Code coverage for Go integration tests](https://go.dev/blog/integration-test-coverage) by Than McIntosh

コードカバレッジツールは、あるテストスイートを実行したときにソースコードのどれだけの割合が実行(カバー)されるかを開発者が把握する助けになります。

Goには以前から、「go test」コマンドの「-cover」フラグを使ってパッケージレベルでコードカバレッジを計測する仕組みが用意されています([Go 1.2リリースで導入されました](https://go.dev/blog/cover))。

このツール群はほとんどの場合うまく機能しますが、より大規模なGoアプリケーションに対してはいくつかの弱点があります。そのようなアプリケーションでは、開発者はパッケージレベルのユニットテストに加えて、プログラム全体の振る舞いを検証する「統合」テストをよく書きます。

この種のテストでは、通常、アプリケーションの完全なバイナリをビルドし、そのバイナリを代表的な一連の入力に対して実行する(サーバーであれば本番相当の負荷の下で実行する)ことで、個々のパッケージを単体でテストするのではなく、すべての構成パッケージが正しく連携して動作していることを確認します。

統合テスト用のバイナリは「go test」ではなく「go build」でビルドされるため、これまでGoのツール群にはこうしたテストのカバレッジプロファイルを収集する簡単な方法がありませんでした。

Go 1.20からは、「go build -cover」でカバレッジ計測用の計装を組み込んだプログラムをビルドできるようになりました。この計装済みバイナリを統合テストに投入することで、カバレッジテストの適用範囲を広げられます。

この記事では、これらの新機能がどのように動作するかを例を挙げて示し、統合テストからカバレッジプロファイルを収集するためのユースケースとワークフローの概要を説明します。

## 例

非常に小さなサンプルプログラムを取り上げ、そのための簡単な統合テストを書き、その統合テストからカバレッジプロファイルを収集してみましょう。

この実習では、[`gitlab.com/golang-commonmark/mdtool`](https://pkg.go.dev/gitlab.com/golang-commonmark/mdtool)にあるMarkdown処理ツール「mdtool」を使います。これは、Markdown-to-HTML変換ライブラリである[`gitlab.com/golang-commonmark/markdown`](https://pkg.go.dev/gitlab.com/golang-commonmark/markdown)パッケージをクライアントがどのように使うかを示すためのデモプログラムです。

## mdtoolのセットアップ

まずは「mdtool」自体をダウンロードしましょう(この手順を再現可能にするため、特定のバージョンを選んでいます)。

```
$ git clone https://gitlab.com/golang-commonmark/mdtool.git
...
$ cd mdtool
$ git tag example e210a4502a825ef7205691395804eefce536a02f
$ git checkout example
...
$
```

## 簡単な統合テスト

次に「mdtool」用の簡単な統合テストを書きます。このテストは「mdtool」のバイナリをビルドしたうえで、一連の入力Markdownファイルに対して実行します。この非常に単純なスクリプトは、テストデータ用ディレクトリ内の各ファイルに対して「mdtool」バイナリを実行し、何らかの出力が生成されクラッシュしないことを確認します。

```
$ cat integration_test.sh
#!/bin/sh
BUILDARGS="$*"
#
# 以下のコマンドが正常に完了しなかった場合はテストを終了する。
#
set -e
#
# テスト用の入力をいくつかダウンロードする('website'リポジトリには様々な*.mdファイルが含まれている)。
#
if [ ! -d testdata ]; then
  git clone https://go.googlesource.com/website testdata
  git -C testdata tag example 8bb4a56901ae3b427039d490207a99b48245de2c
  git -C testdata checkout example
fi
#
# テスト用にmdtoolバイナリをビルドする。
#
rm -f mdtool.exe
go build $BUILDARGS -o mdtool.exe .
#
# 'testdata'内の一連の入力ファイルに対してツールを実行する。
#
FILES=$(find testdata -name "*.md" -print)
N=$(echo $FILES | wc -w)
for F in $FILES
do
  ./mdtool.exe +x +a $F > /dev/null
done
echo "finished processing $N files, no crashes"
$
```

このテストを実行した例を示します。

```
$ /bin/sh integration_test.sh
...
finished processing 380 files, no crashes
$
```

成功です。「mdtool」バイナリが一連の入力ファイルを問題なく処理できることを確認できました……しかし、実際にはツールのソースコードのどれだけを実行できたのでしょうか。次のセクションでは、それを調べるためにカバレッジプロファイルを収集します。

## 統合テストを使ってカバレッジデータを収集する

先ほどのスクリプトを呼び出しつつ、カバレッジ計測用にツールをビルドし、生成されたプロファイルを後処理する、別のラッパースクリプトを書いてみましょう。

```
$ cat wrap_test_for_coverage.sh
#!/bin/sh
set -e
PKGARGS="$*"
#
# セットアップ
#
rm -rf covdatafiles
mkdir covdatafiles
#
# カバレッジ計測用にビルドするため、スクリプトに"-cover"を渡し、
# GOCOVERDIRを設定した状態で実行する。
#
GOCOVERDIR=covdatafiles \
  /bin/sh integration_test.sh -cover $PKGARGS
#
# 生成されたプロファイルを後処理する。
#
go tool covdata percent -i=covdatafiles
$
```

上のラッパースクリプトについて、押さえておきたい点がいくつかあります。

* `integration_test.sh` を実行する際に「-cover」フラグを渡しており、これによってカバレッジ計測が組み込まれた「mdtool.exe」バイナリが得られる
* カバレッジデータファイルの書き込み先ディレクトリとして、GOCOVERDIR環境変数を設定している
* テストが完了すると「go tool covdata percent」を実行し、ステートメントカバー率のレポートを生成する

この新しいラッパースクリプトを実行した際の出力は次の通りです。

```
$ /bin/sh wrap_test_for_coverage.sh
...
    gitlab.com/golang-commonmark/mdtool coverage: 48.1% of statements
$
# 注: この時点でcovdatafilesには381個のファイルが含まれる。
```

できました。これで、統合テストが「mdtool」アプリケーションのソースコードをどの程度実行できているか、おおよその見当がつくようになりました。

テストハーネスを改善する変更を加えて、カバレッジ収集をもう一度実行すると、その変更がカバレッジレポートに反映されるのがわかります。例えば、次の2行を`integration_test.sh`に追加してテストを改善するとしましょう。

```
./mdtool.exe +ty testdata/README.md  > /dev/null
./mdtool.exe +ta < testdata/README.md  > /dev/null
```

カバレッジテスト用のラッパースクリプトを再度実行します。

```
$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$
```

変更の効果が見て取れます。ステートメントカバー率が48%から54%に上がりました。

## カバレッジ対象のパッケージを選択する

デフォルトでは、「go build -cover」はビルド対象のGoモジュールに含まれるパッケージ、つまりこの場合は`gitlab.com/golang-commonmark/mdtool`パッケージのみを計装します。しかし場合によっては、カバレッジ計装を他のパッケージにも広げると便利です。これは「go build -cover」に「-coverpkg」を渡すことで実現できます。

私たちの例のプログラムでは、「mdtool」は実質的に`gitlab.com/golang-commonmark/markdown`パッケージの薄いラッパーに過ぎないため、`markdown`を計装対象のパッケージ群に含めてみると興味深い結果が得られます。

「mdtool」の`go.mod`ファイルは次の通りです。

```
$ head go.mod
module gitlab.com/golang-commonmark/mdtool

go 1.17

require (
    github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
    gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
)
```

「-coverpkg」フラグを使うと、上記の依存パッケージの1つをカバレッジ解析に含めるかどうかなど、対象パッケージを制御できます。例を示します。

```
$ /bin/sh wrap_test_for_coverage.sh -coverpkg=gitlab.com/golang-commonmark/markdown,gitlab.com/golang-commonmark/mdtool
...
    gitlab.com/golang-commonmark/markdown   coverage: 70.6% of statements
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$
```

## カバレッジデータファイルを扱う

カバレッジ計測付きの統合テストが完了し、一連の生データファイル(今回の例では`covdatafiles`ディレクトリの中身)が書き出されたら、これらのファイルをさまざまな方法で後処理できます。

### プロファイルを「-coverprofile」形式のテキストに変換する

ユニットテストの場合は、`go test -coverprofile=abc.txt`を実行すれば、あるカバレッジテストの実行結果をテキスト形式のカバレッジプロファイルとして書き出せます。

`go build -cover`でビルドしたバイナリの場合は、GOCOVERDIRディレクトリに出力されたファイルに対して`go tool covdata textfmt`を実行することで、あとからテキスト形式のプロファイルを生成できます。

この手順が終われば、`go test -coverprofile`のときと同じように、`go tool cover -func=<file>`や`go tool cover -html=<file>`を使ってデータを解釈したり可視化したりできます。

例を示します。

```
$ /bin/sh wrap_test_for_coverage.sh
...
$ go tool covdata textfmt -i=covdatafiles -o=cov.txt
$ go tool cover -func=cov.txt
gitlab.com/golang-commonmark/mdtool/main.go:40:     readFromStdin   100.0%
gitlab.com/golang-commonmark/mdtool/main.go:44:     readFromFile    80.0%
gitlab.com/golang-commonmark/mdtool/main.go:54:     readFromWeb 0.0%
gitlab.com/golang-commonmark/mdtool/main.go:64:     readInput   80.0%
gitlab.com/golang-commonmark/mdtool/main.go:74:     extractText 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:88:     writePreamble   100.0%
gitlab.com/golang-commonmark/mdtool/main.go:111:    writePostamble  100.0%
gitlab.com/golang-commonmark/mdtool/main.go:118:    handler     0.0%
gitlab.com/golang-commonmark/mdtool/main.go:139:    main        51.6%
total:                          (statements)    54.6%
$
```

### 「go tool covdata merge」で生のプロファイルをマージする

「-cover」付きでビルドされたアプリケーションは、実行するたびにGOCOVERDIR環境変数で指定したディレクトリへ1つ以上のデータファイルを書き出します。統合テストがN回プログラムを実行すれば、出力先ディレクトリにはO(N)個のファイルができることになります。データファイルの内容には重複が多いのが普通なので、データを圧縮したり、異なる統合テスト実行から得られたデータセットを結合したりするには、`go tool covdata merge`コマンドでプロファイルをマージできます。例を示します。

```
$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$ ls covdatafiles
covcounters.13326b42c2a107249da22f6e0d35b638.772307.1677775306041466651
covcounters.13326b42c2a107249da22f6e0d35b638.772314.1677775306053066987
...
covcounters.13326b42c2a107249da22f6e0d35b638.774973.1677775310032569308
covmeta.13326b42c2a107249da22f6e0d35b638
$ ls covdatafiles | wc
    381     381   27401
$ rm -rf merged ; mkdir merged ; go tool covdata merge -i=covdatafiles -o=merged
$ ls merged
covcounters.13326b42c2a107249da22f6e0d35b638.0.1677775331350024014
covmeta.13326b42c2a107249da22f6e0d35b638
$
```

`go tool covdata merge`操作は`-pkg`フラグも受け付けており、必要であれば特定のパッケージやパッケージ群を選び出せます。

このマージ機能は、他のテストハーネスによって生成された結果も含め、異なる種類のテスト実行結果を結合する際にも役立ちます。

## まとめ

以上が概要です。1.20リリースにより、Goのカバレッジツール群はパッケージテストだけに限定されず、より大規模な統合テストからもプロファイルを収集できるようになりました。より大規模で複雑なテストがどれだけうまく機能しているか、そしてソースコードのどの部分を実行しているかを理解する助けとして、この新機能をぜひ活用してください。

これらの新機能をぜひ試してみてください。そしていつものように、問題を見つけたら[GitHub issueトラッカー](https://github.com/golang/go/issues)にissueを立ててください。ありがとうございました。

By Than McIntosh

