golangのユニットテスト

前回の記事では、Visual Studio CodeでのGUIデバッグ環境を構築した。今回はユニットテストとCI(継続的インテグレーション)について述べる。

早速開発を始めたいところだが最近の開発ではユニットテストが重要視される。一人で開発するにしてもテストファーストで設計をしてからコーディングするのは重要だと思う。何となくで書いていくと抽象度が酷いことになるので。

golangには標準でユニットテスト機能が備わっている。go testコマンドだ。Visual Studio Codeと連携まではできないようなのでコマンドで実行しよう。テスト用の関数名やファイル名には規則がある。試しに足し算のみの簡単なサンプルを書いてみる。

foo.go

package foo

func Sum(i, j int) int {
    return i + j
}

foo_test.go

package foo

import (
     "testing"
)

func TestSum(t *testing.T) {
    actual := Sum(10, 20)
    expected := 30
    if actual != expected {
        t.Errorf("got %v\nwant %v", actual, expected)
    }
}

func TestSum2(t *testing.T) {
    actual := Sum(1, 2)
    expected := 2
    if actual != expected {
        t.Errorf("got %v\nwant %v", actual, expected)
    }
}

func BenchmarkSum(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Sum(1, 1)
    }
}

実装はfoo.goに記述し、テストケースはfoo_test.goに記述した。テストケースは実装ファイルxxx.goに対してxxx_test.goとする命名規則にすることになっている。テストケースではtestingパッケージをimportする必要がある。テストケースはTest~というprefixを付けることでgo testコマンドはテストケース関数を判別していると思われる。この2つのファイルがあるディレクトリで

go test ./...

と実行するとテストが走る。TestSum2はわざと失敗するように書いてある。失敗するテストがあるとどういう出力になるかも見てほしい。

他にBenchmark~のprefixのテストケースも書いている。これは-benchフラグを付けると実行に要した時間などを計測してくれる。

go test -bench ./...

for文でb.N回まで繰り返すのは結果の揺らぎを抑えるために複数回実行させるため。b.N回が実際に何回になるかは計測結果の分布に依りgolang処理系が自動的に決定する。揺らぎが閾値を下回った時点で停止するのだろう。

より詳細にメモリのアロケーション回数まで調べることもできる。これは-benchmemフラグにより切り替える。

go test -bench ./... -benchmem

golangはやや低レベルの言語なのでメモリアロケーションを抑制するチューニングの余地は大きい。ヒープ周りの動作仕様を確認する上でも有効なので積極的に活用していきたい。