テスト(go test/testing)

Goでは自動テストの仕組みが備わっています。 自動テストはgo testコマンドとtestingパッケージを組み合わせて実現します。

ここでは主に次の内容を説明をします。

  • testingパッケージを使ったテストの書き方
  • ドキュメントにサンプルコードを載せる方法
  • ベンチマークテストの方法
  • go testコマンドの使い方

最初のテスト

はじめに最小限のテストの作成と実行の仕方を説明します。

テスト対象として、次のコードを利用します。

calc.go

package calc

func Max(x, y int) int {
    if x < y {
        return y
    }
    return x
}

テスト用のソースファイルを作成します。

  • ファイル名の末尾は_test.goとする
  • パッケージ名はテスト対象と同じにする(別パッケージも可、以降で説明)
  • テスト関数のシグネチャはfunc TestXxx(t *testing.T)とする(Xxxは小文字で始まらない)
  • testing.T.Errorfを使ってログ出力とテストの失敗報告をする

calc_test.go

package calc

import (
    "testing"
)

func TestMax(t *testing.T) {
    got := Max(1, 2)
    want := 2
    if got != want {
        t.Errorf("Max(1, 2) == %d, want %d", got, want)
    }
}

go testコマンドを使ってテストを実行します。

# ソースファイルがあるディレクトリに移動
cd <your directory>
# 現在のディレクトリのパッケージをテスト
go test

テストの実行結果(合格)が出力されます。

PASS
ok   calc    0.011s

以上でテスト作成と実行ができました。 Goのテストはとてもシンプルに始めることができます。

テスト可能なサンプル

パッケージのドキュメントには、実行可能なサンプルコードがあります(例:fmt.Println)。

このサンプルコードは『ドキュメント用のコメント』ではなく『サンプル用のテストコード』を書くことで、ドキュメントに反映されます。 これによりテスト可能なサンプルコードを素早くドキュメントに提供できます。

サンプルコードの関数名はExampleで始めます。 テストの合否判定はtesting.Tの代わりにコメントを使います。 Output:から始まるコメントとサンプル実行時の標準出力を比較します。 これらが一致するとテスト合格です。 コメントがないとテストは実行されませんので注意してください。

func ExampleMax() {
    fmt.Println(Max(1, 2))
    // Output:
    // 2
}

ドキュメントに掲載される場所は関数名で決まります。

func Example() {}          // パッケージ全体
func ExampleMax() {}       // Max関数またはMax型
func ExampleFile_Read() {} // File型のReadメソッド

同じ識別子に対して複数のサンプルを提供するには、アンダースコアと小文字が続く接頭辞を使用します。

func ExampleMax() {}
func ExampleMax_second() {}
func ExampleMax_third() {}

Unordered output:から始まるコメントは、行の順序は無関係に比較できます。

func Example() {
    fmt.Println(3)
    fmt.Println(2)
    fmt.Println(1)
    // Unordered output:
    // 1
    // 2
    // 3
}

ベンチマークテスト

Goには処理性能を測るベンチマークテストの仕組みが備わっています。

ベンチマークテストの書き方は次の通りです。

  • ベンチマーク関数のシグネチャはfunc BenchmarkXxx(b *testing.B)とする(Xxxは小文字で始まらない)
  • テスト対象の関数をb.N回実行する(b.Nは十分な長さになるよう自動で調整される)
func BenchmarkMax(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Max(1, 2)
    }
}

ベンチマークテストを実行するには、-bench regexpオプションを指定します。 regexpには実行対象を指定します。すべて実行する場合は.を指定します。

go test -bench .
BenchmarkMax-8        1000000000        0.700 ns/op
PASS
ok   calc    0.800s

時間の掛かる前処理がある場合、前処理の後にb.ResetTimerメソッドを実行します。

func BenchmarkMax(b *testing.B) {
    // ここに時間の掛かる前処理があるとする
    fmt.Println("時間のかかる前処理")
    // ベンチマークのタイマーをリセット
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Max(1, 2)
    }
}

テストの書き方

テストの書き方をさらに詳しく説明します。

テスト用のパッケージ

テスト用のパッケージをテスト対象と同じディレクトリに作成できます。 通常、Goのパッケージとディレクトリは1対1ですが、テスト用パッケージは例外です。

  • パッケージ名はxxx_testとする
  • 特別にテスト対象と同じディレクトリに配置する
  • 別パッケージのため、テスト対象の非公開識別子にはアクセスできない
  • パッケージ利用者視点でのテストができる

calc_test.go

package calc_test

import (
    "testing"

    "example/calc"
)

func TestMax(t *testing.T) {
    got := calc.Max(1, 2)
    want := 2
    if got != want {
        t.Errorf("Max(1, 2) == %d, want %d", got, want)
    }
}

テストデータの置き場所

Goのツールはビルドなどでtestdataという名前のディレクトリを無視します。 テストデータはtestdataディレクトリに置きましょう。

testing.Tの主なメソッド

testing.Tの主なメソッドは次の通りです。

  • Name:実行中のテスト名取得
  • Error:ログ出力+テスト失敗報告
  • ErrorF:整形ログ出力+テスト失敗報告
  • Fatal:ログ出力+テスト失敗報告+テストケース終了
  • FatalF:整形ログ出力+テスト失敗報告+テストケース終了
  • Skip:ログ出力+テストスキップ
  • SkipF:整形ログ出力+テストスキップ
  • Log:ログ出力
  • LogF:整形ログ出力
  • Fail:テスト失敗報告
  • FailNow:テスト失敗報告+テストケース終了
  • SkipNow:テストスキップ

サブテスト

t.Runメソッドを使ってサブテストを定義できます。 第一引数はテストの名前、第二引数はテストコードを指定します。

func TestMaxSubtests(t *testing.T) {
    t.Run("正の数", func(t *testing.T) {
        got := Max(1, 2)
        want := 2
        if got != want {
            t.Errorf("Max(1, 2) == %d, want %d", got, want)
        }
    })
    t.Run("負の数", func(t *testing.T) {
        got := Max(-1, -2)
        want := -1
        if got != want {
            t.Errorf("Max(-1, -2) == %d, want %d", got, want)
        }
    })
    t.Run("両方", func(t *testing.T) {
        got := Max(1, -2)
        want := 1
        if got != want {
            t.Errorf("Max(1, -2) == %d, want %d", got, want)
        }
    })
}

テーブル駆動テスト

テストケースのスライスをループしてテストすることをテーブル駆動テストと呼びます。

サブテストと相性が良く、テストケースの追加が簡単にできるのがメリットです。

func TestMaxTableDrivenTests(t *testing.T) {
    tests := []struct {
        name string
        x    int
        y    int
        want int
    }{
        {"正の数", 1, 2, 2},
        {"負の数", -1, -2, -1},
        {"両方", 1, -2, 1},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Max(tt.x, tt.y)
            if got != tt.want {
                t.Errorf("Max(%d, %d) == %d, want %d", tt.x, tt.y, got, tt.want)
            }
        })
    }
}

並列テスト

t.Parallelメソッドを使ってテストを並列に実行できます。

func TestMaxParallelTesting(t *testing.T) {
    // このテストは他の並列テストと並列に実行される
    t.Parallel()
    tests := []struct {
        name string
        x    int
        y    int
        want int
    }{
        {"正の数", 1, 2, 2},
        {"負の数", -1, -2, -1},
        {"両方", 1, -2, 1},
    }
    for _, tt := range tests {
        // ttは並列処理で参照するため、ブロックの内側で変数を宣言する
        tt := tt
        t.Run(tt.name, func(t *testing.T) {
            // サブテストは並列に実行される
            t.Parallel()
            got := Max(tt.x, tt.y)
            if got != tt.want {
                t.Errorf("Max(%d, %d) == %d, want %d", tt.x, tt.y, got, tt.want)
            }
        })
    }
}

テストの前処理・後処理

TestMain関数を使ってテストパッケージ全体の前処理・後処理を実行できます。

func TestMain(t *testing.M) {
    fmt.Println("ここに前処理を書く")
    code := t.Run()
    fmt.Println("ここに後処理を書く")
    os.Exit(code)
}

go testコマンドの使い方

go testコマンドの使い方をさらに詳しく説明します。

テスト対象の指定

テスト対象の指定方法は次の通りです。

# 現在のディレクトリのパッケージ
go test
go test .
# 現在のディレクトリとサブディレクトリのパッケージ
go test ./...
# パッケージを指定
go test fmt
# ディレクトリを指定
go test ./calc
# ファイルを指定
go test calc.go calc_test.go

詳細表示

テストの詳細なログを出力するには-vオプションを指定します。

go test -v ./...

カバレッジ取得

カバレッジを取得するには-coverオプションを指定します。

go test -cover ./...

テストの絞り込み

実行するテストを選択するには-run regexpオプションを指定します。 名前の指定には正規表現を使用できます。 また、スラッシュ区切りで関数名とサブテスト名を指定できます。

# テスト関数名にMaxを含むテストを実行
go test -run Max ./...
# テスト関数名がTestMaxのテストを実行
go test -run "^TestMax$" ./...
# テスト関数名にMaxを含み、サブテスト名にAbcを含むテストを実行
go test -run Max/Abc ./...
# すべてのテスト関数名のうち、サブテスト名にAbcを含むテストを実行
go test -run /Abc ./...

最大並列数

並列実行の最大数を指定するには-parallel nオプションを指定します。

go test -parallel 2 ./...

ベンチマーク関係

ベンチマークでメモリ割り当ての統計を表示するには-benchmemオプションを指定します。

go test -bench . -benchmem ./...

ベンチマークの実行時間を指定するには-benchtime tオプションを指定します。

# 時間はtime.Durationで指定
go test -bench . -benchtime 10s ./...
# 回数指定も可能 形式:Nx
go test -bench . -benchtime 10x ./...

ベンチマークの回数を指定するには-count nオプションを指定します。

go test -bench . -count 3 ./...

ベンチマークで同時に実行するCPUを指定するには-cpuオプションを指定します。

go test -bench . -cpu 1,2,4 ./...

参考リンク