當程式中呼叫另外一個package的函式,則測試時對這個package函式mock的方式如下。
範例環境:
- Go 1.16
- Testify 1.7
範例專案的module名稱為abc.com/demo
。
主程式
以下為主程式的函式、型別及檔案。
model/employee.go
僅定義一個Employee
struct型別,為其他函式中使用的參數。
model/employee.go
package model
type Employee struct {
Id int
Name string
Age int
}
service/calculator.go
中定義了Plus()
函式為測試時要mock的對象。
service/calculator.go
package serivce
func Plus(x, y int) int {
return x + y
}
demo.go
的函式AddAge()
為下面demo_test.go
測試的對象,裡面呼叫了service.Plus()
。
demo.go
package demo
import (
"errors"
"abc.com/demo/model"
"abc.com/demo/serivce"
)
func AddAge(x int, emp model.Employee) (int, error) {
if (emp == model.Employee{}) {
return -1, errors.New("emp is empty")
}
return serivce.Plus(x, emp.Age), nil // call package function
}
測試
若要對AddAge()
中的service.Plus()
做mock則需要對程式進行調整,有以下幾種方式。
方式一:變數mock
把依賴的函式放到package variable變數,改為呼叫變數的函式。例如下面把service.Plus()
放入plus
變數中,AddAge()
中則是呼叫plus()
。
demo.go
package demo
import (
"errors"
"abc.com/demo/model"
"abc.com/demo/serivce"
)
var plus = serivce.Plus // assign dependent function to package variable
func AddAge(x int, emp model.Employee) (int, error) {
if (emp == model.Employee{}) {
return -1, errors.New("emp is empty")
}
return plus(x, emp.Age), nil // call package variable function
}
測試AddAge()
時即可利用package variable plus
對service.Plus()
進行mock。在測試程式中以mock函式替換主程式的plus
變數內容,並將原函式暫存起來,測試結束完後再復原。
demo_test.go
package demo
import (
"testing"
"abc.com/demo/model"
"github.com/stretchr/testify/assert"
)
func TestAddAge(t *testing.T) {
testCase := struct {
x int
emp model.Employee
expected int
}{
1,
model.Employee{Id: 1, Name: "John", Age: 33},
34,
}
// create mock function
plusMock := func(x, y int) int {
return 34
}
originalPlus := plus // store orignal function to another variable
plus = plusMock // replace original function by mock function
// restore package variable after test finished
defer func() { plus = originalPlus }()
actual, _ := AddAge(testCase.x, testCase.emp)
assert.Equal(t, testCase.expected, actual) // PASS
}
方式二:參數mock
調整demo.go
程式,定義一個函式型態PlusFunc
,且AddAge()
函式傳入PlusFunc()
參數作為內部呼叫的對象。
demo.go
package demo
import (
"errors"
"abc.com/demo/model"
)
type PlusFunc func(x, y int) int
func AddAge(x int, emp model.Employee, plus PlusFunc) (int, error) {
if (emp == model.Employee{}) {
return -1, errors.New("emp is empty")
}
return plus(x, emp.Age), nil
}
在測試程式即可對PlusMock
參數做mock並傳入AddAge()
達到mock效果。
demo_test.go
package demo
import (
"testing"
"abc.com/demo/model"
"github.com/stretchr/testify/assert"
)
func TestAddAge(t *testing.T) {
testCase := struct {
x int
emp model.Employee
expected int
}{
1,
model.Employee{Id: 1, Name: "John", Age: 33},
34,
}
plusMock := func(x, y int) int {
return 34
}
actual, _ := AddAge(testCase.x, testCase.emp, plusMock)
assert.Equal(t, testCase.expected, actual) // PASS
}
方式三:介面mock - 型別屬性
調整calculator.go
的Plus()
函式為Calculator
struct型別的方法,實作CalculatorService
介面。
service/calculator.go
package serivce
type CalculatorService interface {
Plus(int, int) int
}
type Calculator struct {
}
func (c Calculator) Plus(x, y int) int {
return x + y
}
調整demo.go
的AddAge()
函式為Demo
struct型別的方法。Demo
的屬性serivce.CalculatorService
,即calculator.go
中定義的介面。
demo.go
package demo
import (
"errors"
"abc.com/demo/model"
"abc.com/demo/serivce"
)
type Demo struct {
service serivce.CalculatorService
}
func (demo *Demo) AddAge(x int, emp model.Employee) (int, error) {
if (emp == model.Employee{}) {
return -1, errors.New("emp is empty")
}
return demo.service.Plus(x, emp.Age), nil
}
在測試程式使用Testify的mock.Mock
取代被測對象的中的依賴,並利用mock.On()
設計呼叫mock方法的預期的輸入參數及回傳值。
demo_test.go
package demo
import (
"testing"
"abc.com/demo/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// define mock type
type CalculatorMock struct {
mock.Mock
}
// use mock to implments CalculatorService's method
func (calMock *CalculatorMock) Plus(x, y int) int {
args := calMock.Called(x, y)
return args.Int(0)
}
func TestAddAge(t *testing.T) {
testCase := struct {
x int
emp model.Employee
expected int
}{
1,
model.Employee{Id: 1, Name: "John", Age: 33},
34,
}
calMock := new(CalculatorMock) // create mock instance
calMock.On("Plus", 1, 33).Return(34) // setup mock method arguments and return value
demo := Demo{calMock} // create struct inject mock
actual, _ := demo.AddAge(testCase.x, testCase.emp)
assert.Equal(t, testCase.expected, actual)
}
方式四:介面mock - 函式參數
類似上面對型別屬性做mock,差別在於這邊是把依賴介面作為參數傳入受測對象。
調整calculator.go
的Plus()
函式為Calculator
struct型別的方法,實作CalculatorService
介面。
service/calculator.go
package serivce
type CalculatorService interface {
Plus(int, int) int
}
type Calculator struct {
}
func (c Calculator) Plus(x, y int) int {
return x + y
}
調整demo.go
的AddAge()
函式傳入參數CalculatorService
,即calculator.go
中定義的介面。
demo.go
package demo
import (
"errors"
"abc.com/demo/model"
"abc.com/demo/serivce"
)
func AddAge(x int, emp model.Employee, calService serivce.CalculatorService) (int, error) {
if (emp == model.Employee{}) {
return -1, errors.New("emp is empty")
}
return calService.Plus(x, emp.Age), nil
}
在測試程式使用Testify的mock.Mock
取代被測對象的中的依賴,並利用mock.On()
設計呼叫mock方法的預期的輸入參數及回傳值。
demo_test.go
package demo
import (
"testing"
"abc.com/demo/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type CalculatorMock struct {
mock.Mock
}
func (calMock *CalculatorMock) Plus(x, y int) int {
args := calMock.Called(x, y)
return args.Int(0)
}
func TestAddAge(t *testing.T) {
testCase := struct {
x int
emp model.Employee
expected int
}{
1,
model.Employee{Id: 1, Name: "John", Age: 33},
34,
}
calMock := new(CalculatorMock) // create mock instance
calMock.On("Plus", 1, 33).Return(34) // setup mock method arguments
actual, _ := AddAge(testCase.x, testCase.emp, calMock)
assert.Equal(t, testCase.expected, actual) // PASS
}
沒有留言:
張貼留言