🧪

【Go】Goのテストに入門してみた! ~カバレッジ編~

に公開

はじめに

前回は「並行テスト」を見ていきました。
今回は「カバレッジ」に入門していきます!

前回の記事はこちら!
https://zenn.dev/tmyhrn/articles/4f67a4cf53f1cb

この記事でわかること

  • テストカバレッジの基本と、それがなぜ重要か
  • Goでのカバレッジ測定方法と -coverフラグ / -coverprofileフラグ の使い方
  • カバレッジ情報の可視化方法と、足りないテストケースの発見・追加の手順

カバレッジとは?

カバレッジとは、テストがどれだけコードを実行したか(網羅したか) を示す割合のことです。
テストカバレッジを測定する目的として、以下の理由を挙げることができます。

  • テストが足りているかを確認できる
  • バグの温床になりやすい「テストされていないコード」を見つけられる
  • 品質向上やリファクタリングの指標にもなる

しかし、カバレッジが100%だとしても、全ての入力に対してバグが発生しないとは限らないので注意しましょう。

テストカバレッジを測定しよう

※テストファイル内の関数はカバレッジ対象外になるのでファイルを分ける

main.go
package main

import (
    "unicode/utf8"
)

func Length(s string) int {
    if len(s) == 0 {
        return 0
    }
    return utf8.RuneCountInString(s)
}
main_test.go
package main

import (
	"testing"
)

func TestLength(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  int
	}{
		{"ascii", "Hello", 5},
		{"full-width-japanese", "こんにちは", 5},
		{"half-width-japanese", "コンニチハ", 5},
		{"emoji", "😃😃😃", 3},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Length(tt.input); got != tt.want {
				t.Errorf("Length = %d, want %d", got, tt.want)
			}
		})
	}
}

テストカバレッジを測定するには、-coverフラグ をつける必要があります。
例えば、以下のように実行することができます。

ターミナル
# go test -v -cover

=== RUN   TestLength
=== RUN   TestLength/ascii
=== RUN   TestLength/full-width-japanese
=== RUN   TestLength/half-width-japanese
=== RUN   TestLength/emoji
--- PASS: TestLength (0.00s)
    --- PASS: TestLength/ascii (0.00s)
    --- PASS: TestLength/full-width-japanese (0.00s)
    --- PASS: TestLength/half-width-japanese (0.00s)
    --- PASS: TestLength/emoji (0.00s)
PASS
coverage: 66.7% of statements
ok      go-test-practice        0.368s

実行すると、今回の場合「coverage: 66.7% of statements」という文言が出力されます。
これはテストによって66.7%のコードが実行されたことを意味します。
なぜ100%になっていないのか、これだけだとわかりません。

カバレッジ情報を出力する

-coverフラグ をつけるとカバレッジテストを実行できます。
その結果を出力するには、-coverprofileフラグ をつけます。

ターミナル
# go test -v -cover -coverprofile=cover.out

mode: set
go-test-practice/main.go:7.27,8.17 1 1
go-test-practice/main.go:8.17,10.3 1 0
go-test-practice/main.go:11.2,11.34 1 1

1. カバレッジの記録モード

「mode: set」はカバレッジの記録モードを指しています。
set は、「このコードブロックが1回でも実行されたらカバーされたと見なす」という方式を意味します。
他にも、count(実行回数を記録)atomic(並行テスト対応) などのモードがあります。
モードを切り替えたい場合、-covermode=count のようにフラグで指定しましょう。

2. テスト実行情報

これは 「どのファイルのどの関数で、何行目から何行目まで(何文か)を何回実行したか」 を指しています。
フォーマットは <ファイル名>:<開始行>.<開始列>,<終了行>.<終了列> <ステートメント数> <実行数> です。

今回の場合だと以下のようになります。

  • ファイル名:go-test-practice/main.go
  • 開始行:7
  • 開始列:27
  • 終了行:8
  • 終了列:17
  • ステートメント数:1
  • 実行数:1

カバレッジ情報を可視化する

-coverprofileフラグ で出力されたカバレッジ情報だけだと、正直見づらいです。
このカバレッジ情報を出力するツールが、Goには標準で用意されています。

ターミナル
# go tool cover -html=cover.out -o cover.html

このコマンドを実行すると、先ほど出力した cover.out というカバレッジ情報ファイルを読み込み、cover.html というファイルを出力することができます。
HTMLファイルを見ることで、どのコードがテストされていないかを直感的に把握できます。

これを見ると、空文字だった場合のテストが網羅されていないことがわかります。
これを元にテストケースを追加します。

最終的なコード

main_test.go
package main

import (
    "testing"
)

func TestLength(t *testing.T) {
    tests := []struct {
        name  string
        input string
        want  int
    }{
        {"ascii", "Hello", 5},
        {"full-width-japanese", "こんにちは", 5},
        {"half-width-japanese", "コンニチハ", 5},
        {"emoji", "😃😃😃", 3},
        // 以下を追加
        {"empty", "", 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Length(tt.input); got != tt.want {
                t.Errorf("Length = %d, want %d", got, tt.want)
            }
        })
    }
}

カバレッジテストを実行すると、以下のような結果が得られます。

ターミナル
=== RUN   TestLength
=== RUN   TestLength/ascii
=== RUN   TestLength/full-width-japanese
=== RUN   TestLength/half-width-japanese
=== RUN   TestLength/emoji
=== RUN   TestLength/empty
--- PASS: TestLength (0.00s)
    --- PASS: TestLength/ascii (0.00s)
    --- PASS: TestLength/full-width-japanese (0.00s)
    --- PASS: TestLength/half-width-japanese (0.00s)
    --- PASS: TestLength/emoji (0.00s)
    --- PASS: TestLength/empty (0.00s)
PASS
coverage: 100.0% of statements
ok      go-test-practice        0.371s

「coverage: 100.0% of statements」と出力され、100%網羅されたことがわかります。
念のため、HTMLも出力してみてみましょう。

こちらでも、100%網羅されたことを確認できました!🎉

まとめ

今回は「カバレッジ」を学びました。

カバレッジは「どれだけコードがテストされているか」を数値で可視化できる便利な指標です。
Goでは、-coverフラグ で簡単にカバレッジを測定でき、-coverprofileフラグgo tool cover を組み合わせることで、テストされていないコードがどこかをHTMLで視覚的に確認できます。
テストの質を上げるためにも、ただ動作確認をするだけでなく、カバレッジチェックを通じて網羅性も意識することが大切です。

次回は「ベンチマーク」に入門します!

参考

https://future-architect.github.io/articles/20250509a/#カバレッジを取得したい
https://gihyo.jp/article/2023/03/tukinami-go-05
https://qiita.com/takehanKosuke/items/4342ca544d205fb36eb0

Discussion