この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
「汝はテストをかかなければならぬ」
ということで、Goでシンプルにテストをかけるモジュールを探していました。
割といろんなプロジェクトでtestifyが使われていたので試してみました。
testifyは?
Goでテストが書きやすくるモジュールです。
以下のようなパッケージが含まれており、テストコードが簡潔にかけます。
assert package
require package
mock package
suite package
今回はそれぞれ試していこうと思います。
インストール
go get github.com/stretchr/testify
assert package
assertパッケージを使うとシンプルにアサーションチェックがかけます。
assertパッケージでは、テストが途中でFailしても後続の処理は続けます。
また、assertパッケージは結果を bool型で受け取ります。
以下のような関数max
に対してアサーションテストを実行してみます。
func max(a, b int) int {
if a > b {
return a
}
return b
}
テストコードのサンプル
func Test_Max_Assert1(t *testing.T) {
assert.Equal(t, max(-1, 1), 1, "they should be equal")
assert.NotEqual(t, max(-1, 1), -1, "they should not be equal")
}
ちなみに、失敗した場合はこんな感じで分かりやすく表示してくれます。
--- FAIL: Test_Max_Assert1 (0.00s)
main_test.go:11:
Error Trace: main_test.go:11
Error: Not equal:
expected: 1
actual : -1
Test: Test_Max_Assert1
Messages: they should be equal
Equal
,NotEqual
だけでなく便利メソッドが多く用意されています。
func Test_Max_Assert2(t *testing.T) {
var obj []string
assert.Nil(t, obj)
obj = []string{"Something"}
if assert.NotNil(t, obj) {
assert.Equal(t, "Something", obj[0])
}
}
require package
requireパッケージはassertパッケージと違い、結果を返さずその場でテストを終了させます。
内部的にはt.FailNow関数が呼び出されます
下記のようなテストコードの場合は、1番目のテストでFailするので、2番目のテストは実行されません。
func Test_Max_require(t *testing.T) {
// 1
require.Equal(t, max(-1, 1), -1, "they should be equal")
// 2
require.Equal(t, max(-2, 2), -2, "they should be equal")
}
mock package
mockパッケージは、実際のオブジェクトの代わりとなモックオブジェクトを簡単に書くことができます。
例えば、下記のような関数isAdult
に対してテストをすることを考えます。
type user interface {
GetAge() int
}
func isAdult(u user) bool {
age := u.GetAge()
if age >= 20 {
return true
} else {
return false
}
}
mockパッケージを使うと、下記のように簡単にuserモックを簡単に作成することができます。
type MockedUser struct {
mock.Mock
}
func (m *MockedUser) GetAge() int {
args := m.Called()
return args.Int(0)
}
func Test_Mock(t *testing.T) {
testObj := new(MockedUser)
testObj.On("GetAge").Return(20)
assert.True(t, isAdult(testObj))
testObj.AssertExpectations(t)
}
少しややこしいですが、モックオブジェクトのGetAgeメソッドに対する戻り値は、testObj.On("GetAge").Return(20)
で指定しています。
そして、args := m.Called()
のCalled
で先ほど指定した戻り値の配列を受け取っています。
AssertExpectations
は、On
とReturn
で指定されたすべてのものが、実際に期待通りに呼び出されたことを保証します。
mockeryを利用すれば、モックコードを自動生成することができるそうです。
suite package
suiteパッケージでは、オブジェクト指向言語に似た形式のテストを書くことが出来ます。
例えば、テストスイートを構造体として構築し、構造体にsetup
/teardown
/testing
などのメソッドを作成し、go test
でテストを実行することができます。
今回は、setup
/teardown
メソッドの実行順序を確認するために、下記のようなテストコードを書いてみました。
type MySuite struct {
suite.Suite
count int
}
func (suite *MySuite) SetupSuite() {
suite.count++
suite.T().Log(suite.count, ": SetupSuite")
}
func (suite *MySuite) TearDownSuite() {
suite.count++
suite.T().Log(suite.count, ": TearDownSuite")
}
func (suite *MySuite) SetupTest() {
suite.count++
suite.T().Log(suite.count, ": SetupTest")
}
func (suite *MySuite) TearDownTest() {
suite.count++
suite.T().Log(suite.count, ": TeardownTest")
}
func (suite *MySuite) BeforeTest(suiteName, testName string) {
suite.count++
suite.T().Log(suite.count, ": BeforeTest")
}
func (suite *MySuite) AfterTest(suiteName, testName string) {
suite.count++
suite.T().Log(suite.count, ": AfterTest")
}
func (suite *MySuite) TestExec1() {
suite.count++
suite.T().Log(suite.count, ": TestExec1")
suite.Equal(true, true)
}
func (suite *MySuite) TestExec2() {
suite.count++
suite.T().Log(suite.count, ": TestExec2")
suite.Equal(true, true)
}
// テスト実行
func TestMySuite(t *testing.T) {
suite.Run(t, new(MySuite))
}
実行結果は下記のようになりました。
=== RUN TestMySuite
main_test.go:55: 1 : SetupSuite
=== RUN TestMySuite/TestExec1
main_test.go:63: 2 : SetupTest
main_test.go:71: 3 : BeforeTest
main_test.go:79: 4 : TestExec1
main_test.go:75: 5 : AfterTest
main_test.go:67: 6 : TeardownTest
=== RUN TestMySuite/TestExec2
main_test.go:63: 7 : SetupTest
main_test.go:71: 8 : BeforeTest
main_test.go:84: 9 : TestExec2
main_test.go:75: 10 : AfterTest
main_test.go:67: 11 : TeardownTest
=== CONT TestMySuite
main_test.go:59: 12 : TearDownSuite
--- PASS: TestMySuite (0.00s)
--- PASS: TestMySuite/TestExec1 (0.00s)
--- PASS: TestMySuite/TestExec2 (0.00s)
まとめ
簡単にですが、testifyの主要機能を紹介させて頂きました。
自分はプロジェクトに導入することで、テストコードの量を結構抑えられました。
どなたかの役に立てば幸いです。