Go 言語のテスト・フレームワーク
(初出: はじめての Go 言語 (on Windows) その7 - Qiita)
前回の続き。
テストコードを書く
Go 言語では最初からテスト・フレームワークが同梱されています。
いまどきの言語はみんなそうですよね。
テストコードを書くには対象のソースファイルと同じフォルダに *_test.go
という名前のファイルを用意します。
まぁ,説明するより書いた方が早いですね。
package modjulian
import (
"os"
"testing"
"time"
)
type mjdnTest struct { //test case for DayNumber
in time.Time //input data
out int64 //expected result
}
var mjdnTests []mjdnTest //test cases for DayNumber
func TestMain(m *testing.M) {
//initialization
mjdnTests = []mjdnTest { //test cases for DayNumber
{time.Date(1969, 12, 31, 0, 0, 0, 0, time.UTC), int64(40586)},
{time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), int64(40587)},
{time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC), int64(57023)},
}
//start test
code := m.Run()
//termination
os.Exit(code)
}
func TestModifiedJulianDayNumber(t *testing.T) {
for _, testCase := range mjdnTests {
result := DayNumber(testCase.in)
if result != testCase.out {
t.Errorf("DayNumber of \"%v\" = %d, want %d.", testCase.in, result, testCase.out)
}
}
}
package
にはテスト対象のパッケージを指定します。import
にはtesting
パッケージを含めます。Test...
で始まる関数名がテスト実行用の関数です。引数にはt *testing.T
を指定します。TestMain()
は特別な関数です。テストの最初に呼び出され,Run()
で他のテスト関数群をキックします。引数にはm *testing.M
を指定します。TestMain()
内で初期化や条件を変えたテストの繰り返しや後始末処理などを行うことができます。
testing
パッケージには,他の言語のテスト・フレームワークによくある assertion 関数がありません1。 FAQ2 によると
一般的なテストフレームワークにおいて条件・制御・出力機構を持つ専用のミニ言語が用意される傾向がありますが、Go言語にはすでにこれらが備わっています。これらを再び作成するより、我々はGo言語のテストを進めたかったのです。このようにしたことで余計な言語を覚える必要がなくなり、テストを直接的かつ理解しやすくしています。
とあります。 テスト駆動型開発の場合,テストコードはそれ自体が設計書として機能しますので,この割り切りは妥当と言えます3。 その代わりテストコードを(ドキュメントとして)きちんと書くのは骨が折れますが(笑)
テストコードが書けたので早速動かしてみましょう。 環境は前回の最後の状態をそのまま引き継ぎます。
テストを行うには go test
コマンドを使います。
以下の例ではパッケージを指定していますが, ./...
と指定すれば全てのパッケージのテストが対象になります。
C:\workspace\jd>go test -v github.com/spiegel-im-spiegel/astrocalc/modjulian
=== RUN TestDayNumber
--- PASS: TestDayNumber (0.00s)
PASS
ok github.com/spiegel-im-spiegel/astrocalc/modjulian 0.229s
これは成功例。じゃあ,元のコードを少しいじってわざと失敗させてみましょうか(なんだかなぁ)。
C:\workspace\jd>go test -v github.com/spiegel-im-spiegel/astrocalc/modjulian
=== RUN TestDayNumber
--- FAIL: TestDayNumber (0.00s)
modjulian_test.go:35: DayNumber of "1969-12-31 00:00:00 +0000 UTC" = 40587, want 40586.
FAIL
exit status 1
FAIL github.com/spiegel-im-spiegel/astrocalc/modjulian 1.566s
エラーレポートを吐く Errorf()
は内部で Fail()
を呼び出し,テスト自体は続行します。
一方 Errorf()
の代わりに Fatalf()
を使うと,内部で FailNow()
を呼び出しテストを中断します。
Go 言語のテスト・フレームワークでは benchmark や coverage もサポートしてますが,今回は割愛します。
テストの自動化(Continuous Integration)
今回のコードは自動化するほどの規模でもないですが,話のついでに Travis CI で自動化しちゃいましょう。 えっと,今回は Travis CI の説明は割愛します。 興味のある方は「ブックマーク」の項を参考にして下さい。
Travis CI でビルド・テストを行うためには .travis.yml
を書く必要がありますが,テストを行うだけなら .travis.yml
の記述は簡単です。
language: go
go:
- 1.4
- 1.5
script:
- go test -v ./...
実行結果はここを参照して下さい。
次回はドキュメントの話。
ブックマーク
- Go の Test に対する考え方 - Qiita
- Goでテストを書く - 成らぬは人の為さぬなりけり
- golang 1.4で追加されたtestingの便利機能(テストの初期化とお片づけ) - Qiita
- Go + Travis CI + Coveralls でCI環境を作る - Qiita
- GithubにあるリポジトリをTravis CI連携する手順 #junitbook - くりにっき
- CI-as-a-ServiceでGo言語プロジェクトの最新ビルドを継続的に提供する | SOTA
- golangでTravis CIを使ってクロスコンパイルするときにハマったところ #golang #travisci - uchimanajet7のメモ
- Go言語のビルド生活を drone.ioで幸せに暮らす #golang - Qiita
- Golang におけるサブテストの並行処理実装について | eureka tech blog
- golangのテストはじめ - Qiita
-
私は組み込みエンジニアなので,プログラミングで assert を多用するのは,エンジニアの怠慢だと思ってしまいます。まぁ,ベクタ・テーブルからゴリゴリ書くってのなら別ですが。 ↩︎