0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go言語】テストコードの書き方と品質担保の基本 〜「動くコード」から「壊れないコード」へ〜

0
Posted at

はじめに:そのGoコード、本当に「動く」と言い切れますか?

「機能実装で手一杯で、テストコードまで手が回らない」
testingパッケージの存在は知っているけど、どう書き始めればいいかわからない」
「テストを書いたつもりだけど、これで品質が担保できているのか自信がない」

Go言語で開発をしていると、こんな悩みを抱える方は多いのではないでしょうか。特にGoは標準ライブラリだけでテストが書けてしまうため、逆に「何が正解なのか」の指針が見えづらく、我流になりがちな領域でもあります。

しかし、テストコードは単なる「動作確認の道具」ではありません。将来のリファクタリングを支え、仕様をドキュメント化し、チーム開発でのデグレを防ぐ「開発資産」 です。

この記事では、Go言語におけるテストコードの基本的な書き方から、実務で使えるテーブル駆動テスト、カバレッジによる品質担保の考え方まで、初心者の方でも今日から実践できる内容を解説します。読み終わる頃には、「テストを書くのが億劫」から「テストがあるから安心して書ける」へと感覚が変わっているはずです。


1. Goのテストは驚くほどシンプルに始められる

Goのテストは、外部ライブラリを一切必要としません。標準の testing パッケージと go test コマンドだけで完結します。

基本ルール

Goのテストコードには、いくつかの明確な決まりごとがあります。

  • テストファイルの名前は _test.go で終わる こと(例: calculator_test.go
  • テスト関数は Test で始まり、引数に *testing.T を取る こと
  • テスト対象と 同じパッケージ に置くのが基本

最初のテストを書いてみる

まずは、シンプルな足し算関数をテストしてみましょう。

// calculator.go
package calculator

func Add(a, b int) int {
    return a + b
}

これに対するテストコードは次のようになります。

// calculator_test.go
package calculator

import "testing"

func TestAdd(t *testing.T) {
    got := Add(2, 3)
    want := 5

    if got != want {
        t.Errorf("Add(2, 3) = %d; want %d", got, want)
    }
}

あとは、ターミナルで以下のコマンドを実行するだけです。

$ go test ./...
ok      example/calculator      0.002s

たったこれだけ。JUnitやpytestのようなフレームワークの学習コストが不要なのが、Goテストの大きな魅力です。

t.Errort.Fatal の使い分け

初心者が最初につまずくポイントが、失敗時のメソッドの使い分けです。

  • t.Errorf: 失敗を記録するが、テストは続行する
  • t.Fatalf: 失敗を記録し、そのテスト関数を 即座に中断 する

例えば、ファイルを開いてその中身を検証するようなテストで、ファイルオープンに失敗した時点で以降の検証は無意味です。こういう場面では t.Fatalf を使います。逆に、複数のフィールドを個別に検証したい場合は、全部の失敗理由を確認したいので t.Errorf が適切です。


2. 実務で必須の「テーブル駆動テスト」

同じロジックに対して複数のパターンを検証したい場面は非常に多いです。境界値、異常系、ゼロ値、nilなど…これらを愚直にテスト関数として書き並べると、コードがすぐに肥大化します。

そこでGoの世界で定番となっているのが テーブル駆動テスト(Table-Driven Tests) です。

func TestAdd_Table(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"正の数同士", 2, 3, 5},
        {"負の数を含む", -1, 1, 0},
        {"ゼロ同士", 0, 0, 0},
        {"大きな数", 1000000, 2000000, 3000000},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

テーブル駆動テストの3つのメリット

  1. テストケースの追加が超簡単:構造体スライスに1行追加するだけ
  2. t.Run によるサブテスト化:どのケースが失敗したか一目瞭然
  3. 仕様書として機能name フィールドを見るだけで、その関数が何を保証しているかが分かる

Goの標準ライブラリのソースコードを読むと、ほぼすべてのテストがこのスタイルで書かれています。まずは「テストはテーブル駆動で書く」を体に染み込ませることが、Goらしいコードへの第一歩です。


3. エラーを扱うテストとヘルパー関数

Goは「エラーを戻り値で返す」言語なので、テストでも当然エラーの検証が頻繁に登場します。

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

このような関数をテストする際は、errors.Iserrors.As を使ってエラーの型を検証するのが望ましいです。

func TestDivide(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Fatal("エラーが返ることを期待したが、nilだった")
    }
}

また、重複する検証ロジックは ヘルパー関数 に切り出せます。その際に忘れてはいけないのが t.Helper() の呼び出しです。

func assertEqual(t *testing.T, got, want int) {
    t.Helper() // これを書くと、失敗時の行番号がヘルパー呼び出し元になる
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

t.Helper() を書き忘れると、失敗メッセージがヘルパー関数内の行を指してしまい、デバッグ時に「どのテストケースで失敗したのか」が非常に分かりにくくなります。


4. カバレッジで品質を「見える化」する

「テストを書いた」と「品質が担保されている」はイコールではありません。どのコードがテストで通っているか を数値で把握することが重要です。

Goでは、カバレッジ計測もコマンド一発です。

# カバレッジを表示
$ go test -cover ./...
ok      example/calculator      0.003s  coverage: 85.7% of statements

# HTMLレポートを生成して視覚的に確認
$ go test -coverprofile=coverage.out ./...
$ go tool cover -html=coverage.out

ブラウザが開き、テストで通ったコードは緑、通っていないコードは赤でハイライトされます。これを見ながら「この分岐、テストしてなかったな」と気づくことが、品質担保の第一歩です。

ただし、カバレッジ100%が目的化するのは危険です。「重要なロジックの分岐がテストされているか」 という視点を常に持ちましょう。


まとめ:今日から実践できる4つのポイント

この記事で解説した内容を振り返ります。

  • 標準のtestingパッケージ だけで、Goのテストは今すぐ始められる
  • t.Errort.Fatal の使い分け で、テストの意図を明確にする
  • テーブル駆動テスト で、拡張性と可読性の高いテストを書く
  • go test -cover で、テストの網羅性を客観的に把握する

テストは「面倒なもの」ではなく、「未来の自分とチームを助ける投資」です。まずは今書いているコードに、1つでもテスト関数を追加することから始めてみてください。それだけで、コードへの向き合い方が確実に変わります。


さらに一歩踏み込みたい方へ

ここまで読んで、「基本はわかったけど、実際にモックを使ったテストやHTTPハンドラーのテスト、並行処理のテストはどう書くんだろう?」と感じた方もいるかもしれません。実務でGoのテストを書き始めると、以下のような壁にぶつかることが多いです。

  • 外部APIやDBに依存する処理をどうテストするか
  • インターフェースを使ったモックの作り方
  • ゴルーチンを含むコードのテスト戦略
  • ベンチマークテストや testing.TB の活用

こうした内容を、手を動かしながら体系的に学びたい方には、Udemyの Go言語のテストの書き方【入門】30問ドリルで使えるスキルを完全習得 という講座が参考になります。

タイトルの通り「30問のドリル形式」で進むので、記事を読むだけでは得られない 「自分で考えて書く」 アウトプットの経験を積むことができます。特に、テストを書くことに慣れていない方が、体系的に手を動かして学ぶ材料として使いやすい構成になっています。

もちろん、まずは今回紹介したテーブル駆動テストやgo test -coverを、ご自身のプロジェクトで試してみるのが最初の一歩です。書籍や公式ドキュメント、こうしたオンライン講座など、ご自身のスタイルに合った学習リソースを組み合わせて、Goのテスト力を育てていってください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?