0%

[Go] Testify 單元測試

前言

Golang 內建就有輕量級的測試框架 testing, 搭配 go test 就可以方便做測試,另外也有其他 testing package, 像是 Testify, GoMockgocheck ,其中 Testify 的社群活躍度較高,也有持續在維護,因此我們選擇搭配 Testify 來撰寫單元測試。

安裝

1
go get github.com/stretchr/testify

使用

簡單的使用範例如下:

1
2
3
4
5
6
7
8
9
10
package main

import (
"testing"
"github.com/stretchr/testify/assert"
)

func TestSomething(t *testing.T) {
assert.True(t, true, "True is true!")
}

Testify 有提供幾個方便的 packages: assert, require , mock and suite , 接下來介紹一下這幾個 package.

Assert package

assert package 提供一些有用的方法,讓我們寫更好的測試:

  • Prints friendly
  • Allows for very testable code
  • Optionally annotate each assertion with a message

使用範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"testing"
"github.com/stretchr/testify/assert"
)

func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")

// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")

// assert for nil (good for errors)
assert.Nil(t, object)

// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
  • assert 會回傳 boolean 表示驗證成功或失敗

如果需要 assert 很多次,可以改用以下寫法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"testing"
"github.com/stretchr/testify/assert"
)

func TestSomething(t *testing.T) {
a := assert.New(t)

// assert equality
a.Equal(123, 123, "they should be equal")

// assert inequality
a.NotEqual(123, 456, "they should not be equal")

// assert for nil (good for errors)
a.Nil(object)

// assert for not nil (good when you expect something)
if a.NotNil(object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
a.Equal("Something", object.Value)
}
}

Require package

requireaasert 很像,但是它不會回傳 bool , 而是會直接終止目前的測試。

Mock package

mock package 提供了一種機制,可以方便的 mock object, 在測試時可以用來替代 real object. 使用範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package main

import (
"testing"
"github.com/stretchr/testify/mock"
)

/*
Test objects
*/

// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{
mock.Mock
}

// DoSomething is a method on MyMockedObject that implements some interface
// and just records the activity, and returns what the Mock object tells it to.
//
// In the real object, this method would do something useful, but since this
// is a mocked object - we're just going to stub it out.
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
}

/*
Actual test functions
*/

// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)

// setup expectations
testObj.On("DoSomething", 123).Return(true, nil)

// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)

// assert that the expectations were met
testObj.AssertExpectations(t)
}

// TestSomethingWithPlaceholder is a second example of how to use our test object to
// make assertions about some target code we are testing.
// This time using a placeholder. Placeholders might be used when the
// data being passed in is normally dynamically generated and cannot be
// predicted beforehand (eg. containing hashes that are time sensitive)
func TestSomethingWithPlaceholder(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)

// setup expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)

// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)

// assert that the expectations were met
testObj.AssertExpectations(t)
}

// TestSomethingElse2 is a third example that shows how you can use
// the Unset method to cleanup handlers and then add new ones.
func TestSomethingElse2(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)

// setup expectations with a placeholder in the argument list
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)

// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)

// assert that the expectations were met
testObj.AssertExpectations(t)

// remove the handler now so we can add another one that takes precedence
mockCall.Unset()

// return false now instead of true
testObj.On("DoSomething", mock.Anything).Return(false, nil)

testObj.AssertExpectations(t)
}

Suite package

suite 可以幫助我們在讓每個測試案例做前置作業或後置操作, 很像是 Python 中的 setUp()tearDown().

使用範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Basic imports
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}

// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
}

// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}

以上就是 Testify 的簡單介紹,在我們寫程式時,記得一定要寫測試來確保我們的 code 是可以正常執行的,也能夠確保每次的改動沒有改壞原本的 code.

References