Go撰寫測試時對依賴的struct及其方法做mock。
事前要求
參考「Golang 資料庫查詢package目錄分類 」程式。
在main.go
新增一函式GetEmployeeNumber()
依賴EmployeeRepository.GetAllEmployees()
查詢全部員工後再取得長度。關鍵在設計時依賴要由外部注入(Dependency Injection)才方便後續的mock,這邊以參數傳入依賴EmployeeRepository
。
main.go
package main
...
type EmployeeRepository interface {
GetAllEmployees() ([]model.Employee, error)
GetEmployeeByID(id int64) (*model.Employee, error)
}
func main() { ... }
func GetEmployeeNumber(er EmployeeRepository) int {
emps, err := er.GetAllEmployees()
if err != nil {
panic("error")
}
return len(emps)
}
Mocking
在main.go
同目錄新增測試檔main_test.go
並撰寫用GetEmployeeNumber()
的測試程式,利用mock來仿造EmployeeRepository.GetAllEmployees()
的結果。
最原本的mock對象為repo/employee.go
的EmployeeRepositoryImpl.GetAllEmployees()
,而mock的第一步是定義介面並讓mock的對象實作該介面。範例已定義介面EmployeeRepository
被EmployeeRepositoryImpl
實作。
第二步是建立取代測試對象原依賴的mock。讓mock實作與被mock對象的介面來達到mock。例如下面EmployeeRepositoryMock
實作了EmployeeRepository
。
第三步是在mock中新增與被mock方法相同簽章的函式欄位,並讓mock方法調用此欄位,如此便能彈性地依不同測試設定不一樣的mock方法邏輯。例如下面EmployeeRepositoryMock
的getAllEmployeesFn
函式欄位與被mock方法EmployeeRepository.GetAllEmployees()
的簽章相同,並被mock方法EmployeeRepositoryMock.GetAllEmployees()
調用,所以在撰寫測試程式時透過設定getAllEmployeesFn
的值來抽換不同的mock邏輯。
main_test.go
package main
import (
"errors"
"testing"
"abc.com/demo/model"
)
// EmployeeRepositoryMock implements EmployeeRepository interface to mock EmployeeRepositoryImpl
type EmployeeRepositoryMock struct {
// for setting mock impl in test
getAllEmployeesFn func() ([]model.Employee, error)
getEmployeeByIDFn func(id int64) (*model.Employee, error)
}
// implements EmployeeRepository.GetAllEmployees
func (erMock *EmployeeRepositoryMock) GetAllEmployees() ([]model.Employee, error) {
return erMock.getAllEmployeesFn()
}
// implements EmployeeRepository.GetEmployeeByID
func (erMock *EmployeeRepositoryMock) GetEmployeeByID(id int64) (*model.Employee, error) {
return erMock.getEmployeeByIDFn(id)
}
func TestGetEmployeeNumber(t *testing.T) {
testCase := struct{ mock, expected int }{10, 10}
erMock := &EmployeeRepositoryMock{}
// set mock impl for EmployeeRepositoryMock.GetAllEmployees
erMock.getAllEmployeesFn = func() ([]model.Employee, error) {
return make([]model.Employee, testCase.mock), nil
}
result := GetEmployeeNumber(erMock)
if result != testCase.expected {
t.Errorf("expect %v, but %v", testCase.expected, result)
}
}
func TestGetEmployeeNumber_Panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("expect panic but success")
}
}()
erMock := &EmployeeRepositoryMock{}
// set mock impl for EmployeeRepositoryMock.GetAllEmployees
erMock.getAllEmployeesFn = func() ([]model.Employee, error) {
return nil, errors.New("error")
}
GetEmployeeNumber(erMock)
}
關係圖。
┌─────────────────────┐
│ Client │
│ (GetEmployeeNumber) ├ ─ ─ ─ ─ ─ ─┐
└─────────────────────┘ depends
│
▼
┌───────────────────────┐
┌───►│ (I)EmployeeRepository │◄───┐
│ └───────────────────────┘ │
impl impl
│ │
┌───────────┴────────────┐ ┌───────────┴────────────┐
│ EmployeeRepositoryMock │ │ EmployeeRepositoryImpl │
└────────────────────────┘ └────────────────────────┘
專案目錄結構如下。
go-demo/
├── db/
│ └── db.go
├── model/
│ └── employee.go
├── repo/
│ └── employee.go
├── go.mod
├── go.sum
├── main_test.go
└── main.go
測試
專案根目錄輸入go test -v
結果如下。
~/../go-demo$ go test -v
=== RUN TestGetEmployeeNumber
--- PASS: TestGetEmployeeNumber (0.00s)
=== RUN TestGetEmployeeNumber_Panic
--- PASS: TestGetEmployeeNumber_Panic (0.00s)
PASS
ok abc.com/demo 0.113s
沒有留言:
張貼留言