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

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

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

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

スポンサーリンク

背景

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

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

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

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

レガシーテキスト形式への変換

go test -coverprofile=<outfile>” によって生成されるレガシーテキスト形式に、バイナリカバレッジデータファイルを変換するには、covdataのtextfmtセレクターを使用します。生成されたテキストファイルは、”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%
$

解説

textfmt モード: テキスト形式への変換

なぜ変換が必要なのか

統合テストのカバレッジデータ(バイナリ形式)を、従来の単体テストで使われていたテキスト形式に変換することで、既存のツールが使えるようになります。

図書館の例え:

  • バイナリ形式 = 新しいデジタル蔵書システム(最新だが対応ツールが少ない)
  • テキスト形式 = 従来の紙の目録(古いが多くの人が使える)
  • textfmt = デジタルデータを紙の目録形式に変換

基本的な使い方

コマンド構文

go tool covdata textfmt -i=<入力ディレクトリ> -o=<出力ファイル>

具体例:

# バイナリデータをテキストに変換
go tool covdata textfmt -i=coverage -o=profile.txt

完全な作業フロー

# ステップ1: カバレッジ対応でビルド
go build -cover -o myapp .

# ステップ2: テストを実行してバイナリデータ収集
mkdir coverage
GOCOVERDIR=coverage ./myapp

# ステップ3: バイナリデータをテキスト形式に変換
go tool covdata textfmt -i=coverage -o=profile.txt

# ステップ4: テキストファイルの内容を確認(オプション)
cat profile.txt

# ステップ5: 既存ツールでレポート生成
go tool cover -func=profile.txt     # 関数ごとのレポート
go tool cover -html=profile.txt     # HTMLレポート

テキスト形式ファイルの構造

プロファイルファイルの内容

mode: set
mydomain.com/myprogram.go:10.13,12.2 1 1
mydomain.com/greetings/greetings.go:3.23,5.2 1 1

各行の意味:

1行目: mode: set

  • カバレッジの測定モード
  • set: 実行されたかどうか(0または1)
  • 他のモード: count(実行回数)、atomic(並行実行対応)

2行目以降: カバレッジデータ

mydomain.com/myprogram.go:10.13,12.2 1 1
↑                        ↑       ↑ ↑
ファイルパス              位置    文 実行
                                  数 状況
要素説明
ファイルパスソースファイルの場所mydomain.com/myprogram.go
位置情報開始行.開始列,終了行.終了列10.13,12.2
ステートメント数このブロックのステートメント数1
実行状況実行されたか(1=実行、0=未実行)1

位置情報の詳細

10.13,12.2
↑  ↑  ↑  ↑
│  │  │  └─ 終了列(2列目)
│  │  └──── 終了行(12行目)
│  └─────── 開始列(13列目)
└────────── 開始行(10行目)

実際のコード例:

// myprogram.go
 1: package main
 2:
 3: import "fmt"
 4:
 5: func main() {
 6:     message := getMessage()
 7:     fmt.Println(message)
 8: }
 9:
10: func getMessage() string {
11:     return "Hello, World!"
12: }

プロファイル行 myprogram.go:10.13,12.2 1 1 は:

  • 10行目の13列目から始まる(string {
  • 12行目の2列目で終わる(}
  • このブロックには1つのステートメントがある
  • 実行された(1)

go tool cover との連携

パターン1: 関数ごとのレポート(-func)

$ go tool cover -func=profile.txt
mydomain.com/greetings/greetings.go:3:  Goodbye     100.0%
mydomain.com/myprogram.go:10:           getMessage  100.0%
mydomain.com/myprogram.go:5:            main        100.0%
total:                                  (statements) 100.0%

出力の読み方:

ファイル名:行番号:    関数名        カバレッジ率

パターン2: HTMLレポート(-html)

# HTMLレポートを生成して自動的にブラウザで開く
go tool cover -html=profile.txt

# HTMLファイルとして保存
go tool cover -html=profile.txt -o=coverage.html

HTMLレポートの特徴:

  • 実行されたコードは緑色でハイライト
  • 実行されなかったコードは赤色でハイライト
  • 実行されなかったコードは灰色(未測定)
  • 視覚的にわかりやすい

実践例1: 単純なプログラム

# プログラム
$ cat main.go
package main

import "fmt"

func main() {
    fmt.Println(greet("World"))
}

func greet(name string) string {
    if name == "" {
        return "Hello, Stranger!"
    }
    return "Hello, " + name + "!"
}

# ビルドと実行
$ go build -cover -o myapp .
$ mkdir coverage
$ GOCOVERDIR=coverage ./myapp

# テキスト形式に変換
$ go tool covdata textfmt -i=coverage -o=profile.txt

# 関数ごとのカバレッジ
$ go tool cover -func=profile.txt
main.go:5:      main        100.0%
main.go:9:      greet       66.7%
total:          (statements) 75.0%

解釈:

  • main関数: 100%(すべて実行)
  • greet関数: 66.7%(空文字チェックが実行されていない)
  • 全体: 75%

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

#!/bin/bash
# web-coverage.sh

# 準備
rm -rf coverage
mkdir coverage

# ビルド
go build -cover -o webserver ./cmd/server

# サーバー起動
GOCOVERDIR=coverage ./webserver &
SERVER_PID=$!
sleep 2

# テスト実行
curl http://localhost:8080/api/users
curl http://localhost:8080/api/posts
curl http://localhost:8080/health

# サーバー停止
kill $SERVER_PID
wait $SERVER_PID 2>/dev/null

# テキスト形式に変換
go tool covdata textfmt -i=coverage -o=web-coverage.txt

# HTMLレポート生成
go tool cover -html=web-coverage.txt -o=web-coverage.html

# 関数ごとのレポート
echo "=== 関数ごとのカバレッジ ==="
go tool cover -func=web-coverage.txt

echo ""
echo "HTMLレポートを生成しました: web-coverage.html"

レガシー形式と新形式の比較

単体テスト(従来の方法)

# 直接テキスト形式のプロファイルを生成
go test -coverprofile=coverage.txt

# レポート生成
go tool cover -func=coverage.txt
go tool cover -html=coverage.txt

統合テスト(Go 1.20以降)

# バイナリ形式で収集
go build -cover -o myapp .
GOCOVERDIR=coverage ./myapp

# テキスト形式に変換(←この追加ステップが必要)
go tool covdata textfmt -i=coverage -o=coverage.txt

# レポート生成(ここからは同じ)
go tool cover -func=coverage.txt
go tool cover -html=coverage.txt

メリット:

  • 既存のgo tool coverがそのまま使える
  • HTMLレポートが生成できる
  • CIツールとの互換性が保たれる

複数ディレクトリの統合

# 複数のテストスイートを実行
GOCOVERDIR=coverage/unit ./myapp unit-tests
GOCOVERDIR=coverage/integration ./myapp integration-tests
GOCOVERDIR=coverage/e2e ./myapp e2e-tests

# すべてを統合してテキスト形式に変換
go tool covdata textfmt \
  -i=coverage/unit,coverage/integration,coverage/e2e \
  -o=combined-coverage.txt

# HTMLレポート生成
go tool cover -html=combined-coverage.txt -o=full-report.html

CI/CDパイプラインでの活用

# .github/workflows/coverage.yml
name: Coverage Report

on: [push]

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: 1.20
      
      - name: Build with coverage
        run: go build -cover -o myapp .
      
      - name: Run integration tests
        run: |
          mkdir coverage
          GOCOVERDIR=coverage ./myapp run-tests
      
      - name: Convert to text format
        run: go tool covdata textfmt -i=coverage -o=coverage.txt
      
      - name: Generate HTML report
        run: go tool cover -html=coverage.txt -o=coverage.html
      
      - name: Upload coverage report
        uses: actions/upload-artifact@v2
        with:
          name: coverage-report
          path: coverage.html
      
      - name: Display coverage
        run: go tool cover -func=coverage.txt

トラブルシューティング

問題1: 空のプロファイルファイル

$ go tool covdata textfmt -i=coverage -o=profile.txt
$ cat profile.txt
mode: set
# データがない

原因:

  • カバレッジデータが収集されていない
  • GOCOVERDIRを設定せずに実行した

解決策:

# データを確認
ls coverage/

# 正しく実行し直す
GOCOVERDIR=coverage ./myapp

問題2: go tool coverでエラー

$ go tool cover -func=profile.txt
cover: invalid function coverage line: ...

原因:

  • プロファイルファイルが壊れている
  • 古い形式のファイル

解決策:

# 再生成
go tool covdata textfmt -i=coverage -o=profile.txt -pkg=./...

問題3: HTMLが正しく表示されない

$ go tool cover -html=profile.txt
# ソースコードが見つからない

原因:

  • ソースファイルのパスが変わった
  • 元のソースコードがない

解決策:

# カバレッジ収集と同じディレクトリで実行
cd /path/to/project
go tool cover -html=profile.txt

実用的なスクリプト

#!/bin/bash
# generate-full-report.sh

set -e  # エラーで停止

# 設定
APP="./myapp"
COVERAGE_DIR="coverage"
PROFILE="profile.txt"
HTML_REPORT="coverage-report.html"

# クリーンアップ
echo "クリーンアップ中..."
rm -rf $COVERAGE_DIR $PROFILE $HTML_REPORT

# ビルド
echo "ビルド中..."
go build -cover -o $APP .

# テスト実行
echo "統合テストを実行中..."
mkdir $COVERAGE_DIR

# 複数シナリオを実行
scenarios=("scenario1" "scenario2" "scenario3")
for scenario in "${scenarios[@]}"; do
    echo "  - $scenario"
    GOCOVERDIR=$COVERAGE_DIR $APP --test=$scenario
done

# テキスト形式に変換
echo "プロファイルを生成中..."
go tool covdata textfmt -i=$COVERAGE_DIR -o=$PROFILE

# 関数ごとのカバレッジを表示
echo ""
echo "=== カバレッジサマリー ==="
go tool cover -func=$PROFILE | tail -1

# 詳細レポート
echo ""
echo "=== 関数ごとの詳細 ==="
go tool cover -func=$PROFILE

# HTMLレポート生成
echo ""
echo "HTMLレポートを生成中..."
go tool cover -html=$PROFILE -o=$HTML_REPORT

echo ""
echo "完了!"
echo "  テキストレポート: $PROFILE"
echo "  HTMLレポート: $HTML_REPORT"

# カバレッジが閾値を下回ったら警告
COVERAGE=$(go tool cover -func=$PROFILE | tail -1 | awk '{print $3}' | sed 's/%//')
THRESHOLD=80

if (( $(echo "$COVERAGE < $THRESHOLD" | bc -l) )); then
    echo ""
    echo "⚠️  警告: カバレッジが${THRESHOLD}%を下回っています (現在: ${COVERAGE}%)"
    exit 1
fi

まとめ

項目説明
コマンドgo tool covdata textfmt -i=<dir> -o=<file>
入力バイナリカバレッジデータ(GOCOVERDIR
出力テキスト形式プロファイル
互換性go tool cover -func-htmlで使用可能
用途既存ツールやCIパイプラインとの統合

重要なポイント:

  • textfmtでバイナリ→テキスト変換
  • 変換後は従来のgo tool coverが使える
  • HTMLレポートで視覚的に確認できる
  • CI/CDパイプラインに組み込みやすい
  • 複数ディレクトリを統合可能

次の記事では、さらに高度なcovdataの使い方(マージ、フィルタリングなど)を見ていきましょう。

おわりに 

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

よっしー
よっしー

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

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

コメント

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