Go言語入門:統合テストのカバレッジ -Vol.2-

スポンサーリンク
Go言語入門:統合テストのカバレッジ -Vol.2- ノウハウ
Go言語入門:統合テストのカバレッジ -Vol.2-
この記事は約9分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語の統合テストのカバレッジついて解説しています。

スポンサーリンク

背景

Go言語でテストを書いていると、「go test -coverprofileでカバレッジが取れるのは知っているけど、統合テストのカバレッジってどうやって測るんだろう?」と疑問に思ったことはありませんか?

公式ドキュメントには、Go 1.20から統合テストのカバレッジ測定がサポートされたことが書かれていますが、英語で書かれている上に、ユニットテストとの違いや具体的な手順の説明が簡潔すぎて、初めて読むと「結局どうすればいいの?」と戸惑ってしまうかもしれません。

この記事では、公式ドキュメントの内容を丁寧な日本語に翻訳し、さらに初心者の方でも理解できるように、ユニットテストと統合テストの違い、なぜ3ステップ必要なのか、そして実際にどのようなコマンドを実行すればよいのかを、具体例を交えて解説していきます。

統合テストのカバレッジ測定は一見難しそうに見えますが、仕組みを理解すれば決して複雑ではありません。実際のアプリケーションでどれだけコードがテストされているかを把握することで、より品質の高いソフトウェア開発ができるようになります。一緒に学んでいきましょう!

カバレッジプロファイリングのためのバイナリのビルド

カバレッジプロファイルを収集するアプリケーションをビルドするには、アプリケーションバイナリのターゲットに対して go build を呼び出す際に -cover フラグを渡します。go build -cover の呼び出し例については、以下のセクションを参照してください。生成されたバイナリは、環境変数の設定を使用して実行することで、カバレッジプロファイルを取得できます(実行方法については次のセクションを参照してください)。

インストルメンテーション対象のパッケージの選択方法

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

例えば、以下はmainパッケージ、ローカルのメインモジュールパッケージである greetings、そしてモジュール外からインポートされた一連のパッケージ(rsc.io/quotefmt など)を含むサンプルプログラムです(完全なプログラムへのリンク)。

$ 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” コマンドラインフラグを付けてビルドして実行すると、プロファイルに含まれるのは正確に2つのパッケージだけです:mainmydomain.com/greetings。他の依存パッケージは除外されます。

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

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

上記のビルドでは、mydomain.com のmainパッケージと、rsc.io/quote および io パッケージがプロファイリング対象として選択されます。mydomain.com/greetings は明示的にリストされていないため、メインモジュールに含まれているにもかかわらず、プロファイルから除外されます。


解説

カバレッジ測定可能なバイナリのビルド方法

通常のビルドコマンドに -cover フラグを追加するだけです:

# 通常のビルド
go build -o myapp .

# カバレッジ測定可能なビルド
go build -cover -o myapp .

何が起こるのか:

  • コンパイラがコードに「カバレッジ測定用の仕組み」を埋め込む
  • このプロセスを「インストルメンテーション」と呼びます
  • 実行時にどのコードが実行されたかを記録できるようになる

レストランの例え:

  • 通常のビルド = 普通に料理を提供する厨房
  • カバレッジビルド = 各料理に「何回作られたか」をカウントするセンサーを付けた厨房

インストルメンテーション(instrumentation)とは

**instrumentation(インストルメンテーション)**は「計測器を取り付ける」という意味です。

プログラムのコードに「ここが実行されました」と記録する仕組みを自動で埋め込むことを指します。

// 元のコード
func Add(a, b int) int {
    return a + b
}

// インストルメンテーション後(概念的なイメージ)
func Add(a, b int) int {
    coverageRecorder.Mark("Add function called")  // 追加される
    return a + b
}

どのパッケージが測定対象になるのか

デフォルトの動作(-cover のみ)

測定される:

  • ✅ 自分のプロジェクト(メインモジュール)内のパッケージ

測定されない:

  • ❌ 外部ライブラリ(go.mod にある依存パッケージ)
  • ❌ Go標準ライブラリ(fmtio など)

サンプルプログラムで理解する

プロジェクト構成:
mydomain.com/               ← メインモジュール(自分のプロジェクト)
├── go.mod
├── myprogram.go           ← mainパッケージ
└── greetings/
    └── greetings.go       ← greetingsパッケージ

外部依存:
rsc.io/quote               ← 外部ライブラリ
fmt                        ← Go標準ライブラリ

go build -cover を実行した場合:

測定対象:
✅ main パッケージ(mydomain.com)
✅ greetings パッケージ(mydomain.com/greetings)

測定対象外:
❌ rsc.io/quote(外部ライブラリ)
❌ fmt(標準ライブラリ)
❌ その他の依存パッケージ

なぜ外部パッケージは含まれないのか

理由:

  1. 自分のコードの品質を知りたいから
    • 外部ライブラリは既にテストされているはず
    • 自分が書いたコードのカバレッジが重要
  2. パフォーマンスの問題
    • すべてのパッケージを測定すると処理が重くなる
    • ファイルサイズも大きくなる
  3. 実用性
    • 標準ライブラリのカバレッジを知っても意味がない

図書館の例え:

  • 自分が書いた本(メインモジュール)の読まれ方を調べたい
  • 他の著者の本(外部ライブラリ)は既に評価済み
  • すべての本を調べると時間とコストがかかりすぎる

-coverpkg フラグで測定対象をカスタマイズ

特定のパッケージだけを測定したい場合や、外部パッケージも含めたい場合に使います。

構文

go build -cover -coverpkg=パッケージ1,パッケージ2,... -o 出力ファイル名 .

例1: 外部ライブラリも含める

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

この場合の測定対象:

✅ main(mydomain.comのmainパッケージ)
✅ io(標準ライブラリ)
✅ rsc.io/quote(外部ライブラリ)

❌ mydomain.com/greetings(明示的に指定していない!)

重要な注意点: -coverpkg を使うと、指定したパッケージだけが測定対象になります。デフォルトの「メインモジュール全体」という動作は無効化されます。

例2: メインモジュール全体を指定

# メインモジュールのすべてのパッケージを測定
go build -cover -coverpkg=mydomain.com/... -o myapp.exe .

... は「このパス以下のすべて」を意味します。

実際の使い分け

ケース1: 通常の開発(デフォルトで十分)

go build -cover -o myapp .
  • 自分のコードだけ測定
  • 最もシンプル
  • ほとんどの場合これで十分

ケース2: 特定のパッケージに絞りたい

# APIハンドラーだけ測定したい
go build -cover -coverpkg=mydomain.com/api/handlers -o myapp .

ケース3: 外部ライブラリの使い方を確認したい

# 自分のプロジェクト + 特定の外部ライブラリ
go build -cover -coverpkg=mydomain.com/...,github.com/external/lib -o myapp .

実践例:Webアプリケーション

# プロジェクト構成
mywebapp/
├── main.go
├── handlers/
│   ├── user.go
│   └── post.go
├── models/
│   └── database.go
└── utils/
    └── helper.go

# 例1: すべて測定(デフォルト)
go build -cover -o webapp .
→ main, handlers, models, utils すべて測定

# 例2: ハンドラーだけ測定
go build -cover -coverpkg=mywebapp/handlers -o webapp .
→ handlers パッケージのみ測定

# 例3: ハンドラーとモデルだけ測定
go build -cover -coverpkg=mywebapp/handlers,mywebapp/models -o webapp .
→ handlers と models だけ測定

トラブルシューティング

問題1: 自分のパッケージが測定されない

# ❌ 間違い
go build -cover -coverpkg=io,fmt -o myapp .
# 自分のパッケージを指定し忘れている

# ✅ 正解
go build -cover -coverpkg=mydomain.com/...,io,fmt -o myapp .

問題2: バイナリサイズが大きすぎる

# インストルメンテーションによってサイズが増加
# 本番環境では -cover なしでビルド

# 開発/テスト用
go build -cover -o myapp-test .

# 本番用
go build -o myapp .

まとめ

項目説明
基本コマンドgo build -cover -o バイナリ名 .
デフォルト動作メインモジュール内のパッケージのみ測定
カスタマイズ-coverpkg で測定対象を明示的に指定
注意点-coverpkg を使うとデフォルト動作が無効化される
ベストプラクティス通常はデフォルトで十分。特別な理由がなければ -coverpkg は不要

次の記事では、ビルドしたバイナリを実際に実行してカバレッジデータを収集する方法を見ていきます。

おわりに 

本日は、Go言語の統合テストのカバレッジについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました