帰ってきた「しっぽのさきっちょ」

Go 言語のテスト・フレームワーク — プログラミング言語 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)
		}
	}
}
  1. package にはテスト対象のパッケージを指定します。
  2. import には testing パッケージを含めます。
  3. Test... で始まる関数名がテスト実行用の関数です。引数には t *testing.T を指定します。
  4. TestMain() は特別な関数です。テストの最初に呼び出され, Run() で他のテスト関数群をキックします。引数には m *testing.M を指定します。 TestMain() 内で初期化や条件を変えたテストの繰り返しや後始末処理などを行うことができます。

testing パッケージには,他の言語のテスト・フレームワークによくある assertion 関数がありません1FAQ2 によると

一般的なテストフレームワークにおいて条件・制御・出力機構を持つ専用のミニ言語が用意される傾向がありますが、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 言語に関するブックマーク集はこちら


  1. オリジナルは http://golang-jp.org/doc/faq#assertions [return]
  2. オリジナルは http://golang-jp.org/doc/faq#testing_framework [return]
  3. 私は組み込みエンジニアなので,プログラミングで assert を多用するのは,エンジニアの怠慢だと思ってしまいます。まぁ,ベクタ・テーブルからゴリゴリ書くってのなら別ですが。 [return]