目录
In this page, i integrate GoConvey, sqlmock, go generate and GoMock to test golang code.
1. GoConvey
GoConvey is a yummy Go testing tool for gophers. Works with go test
.
Features:
-
Directly integrates with
go test
-
Fully-automatic web UI (works with native Go tests, too)
-
Huge suite of regression tests
-
Shows test coverage (Go 1.2+)
-
Readable, colorized console output (understandable by any manager, IT or not)
-
Test code generator
-
Desktop notifications (optional)
-
Immediately open problem lines in Sublime Text (some assembly required)
Installation
$ go get github.com/smartystreets/goconvey
Example
// UnmarshalToMap unmarshal xml into map in util.go
func UnmarshalToMap(b []byte) map[string]interface{} {
decoder := xml.NewDecoder(bytes.NewBuffer(b))
s := NewStack()
for {
t, err := decoder.Token()
if err != nil {
if err == io.EOF {
break
}
}
switch ele := t.(type) {
case xml.StartElement:
s.Push(ele)
case xml.CharData:
s.Push(string(xml.CharData(ele)))
case xml.EndElement:
entry := make(map[string]interface{})
var val interface{}
for {
p := s.Pop()
// push key [start] val into stack
if start, ok := p.(xml.StartElement); ok {
node := make(map[string]interface{})
node[start.Name.Local] = val
s.Push(node)
break
}
switch p.(type) {
case map[string]interface{}:
// handle pushed map
node := p.(map[string]interface{})
for k, v := range node {
entry[k] = v
}
if val == nil {
val = entry
}
default:
// handle xml char data
val = p
}
}
}
}
return s.Pop().(map[string]interface{})
}
// in util_test.go
func TestUnmarshalToMap(t *testing.T) {
convey.Convey("Failed unmarshal xml to map", t, func() {
b := []byte(`<?xml version="1.0"?><xml><appid><![CDATA[wxxxx]]></appid><case><cash_fee><![CDATA[1]]></cash_fee></case><mch_id><![CDATA[xxx]]></mch_id><nonce_str><![CDATA[KptCSXZh1qBjK8wb]]></nonce_str><out_refund_no_0><![CDATA[1519535580802]]></out_refund_no_0><out_trade_no><![CDATA[1515845954891]]></out_trade_no><refund><refund_account_0><![CDATA[REFUND_SOURCE_UNSETTLED_FUNDS]]></refund_account_0><refund_channel_0><![CDATA[ORIGINAL]]></refund_channel_0><refund_count>1</refund_count><refund_fee>1</refund_fee><other_refund><refund_fee_0>1</refund_fee_0><refund_id_0><![CDATA[50000405502018022503594217469]]></refund_id_0><refund_recv_accout_0><![CDATA[支付用户的零钱]]></refund_recv_accout_0><refund_status_0><![CDATA[SUCCESS]]></refund_status_0></other_refund><refund_success_time_0><![CDATA[2018-02-25 13:13:03]]></refund_success_time_0></refund><result_code><![CDATA[SUCCESS]]></result_code><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><sign><![CDATA[E8A30F02296C6169860A92C2D52AD5A8]]></sign><total_fee><![CDATA[1]]></total_fee><transaction_id><![CDATA[4200000100201801133414066940]]></transaction_id></xml>`)
m := UnmarshalToMap(b)
convey.So(m["xml"].(map[string]interface{})["appid"], assertions.ShouldEqual, "wxxxx")
convey.So((m["xml"].(map[string]interface{})["case"].(map[string]interface{}))["cash_fee"], assertions.ShouldEqual, "1")
convey.So(((m["xml"].(map[string]interface{})["refund"].(map[string]interface{}))["other_refund"].(map[string]interface{}))["refund_fee_0"], assertions.ShouldEqual, "1")
})
}
2. Mock DB
sqlmock is a mock library implementing sql/driver. Which has one and only purpose - to simulate any sql driver behavior in tests, without needing a real database connection.
-
supports concurrency and multiple connections.
-
supports go1.8 Context related feature mocking and Named sql parameters.
-
does not require any modifications to your source code.
-
the driver allows to mock any sql driver method behavior.
-
has strict by default expectation order matching.
-
has no third party dependencies.
Install
go get github.com/DATA-DOG/go-sqlmock
Example
func mockGormDB() (*gorm.DB, sqlmock.Sqlmock, error) {
db, mock, err := sqlmock.New()
if err != nil {
return nil, nil, err
}
// GORM
gormDB, err := gorm.Open("mysql", db) // Can be any connection string
if err != nil {
return nil, nil, err
}
gormDB.LogMode(true)
return gormDB, mock, nil
}
func TestMySQLChannelRepository_Get(t *testing.T) {
Convey("Test ChannelRepository Get is failed!", t, func() {
db, mock, err := mockGormDB()
So(err, ShouldBeNil)
defer db.Close()
now := time.Now()
rows := sqlmock.NewRows([]string{"id", "chl_type", "chl_name", "chl_label", "chl_desc", "chl_logo", "state", "created_at", "created_by", "updated_at", "updated_by"}).
AddRow(1, "wx_app", "微信App支付", "微信App支付", "微信 App 支付", "wx_app.png", 1, &now, "admin", &now, "admin")
mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `op_channel` WHERE (`op_channel`.`id` = 1)")).
WillReturnRows(rows)
chlRepo := MySQLChannelRepository{
GormRepository: gd.GormRepository{DB: db},
}
var chl *model.OpChannel
chl, err = chlRepo.Get(1)
So(err, ShouldBeNil)
So(chl.Id, ShouldEqual, 1)
})
}
3. Generating code
go generateis a command to generating code buildin golang release 1.4 and later.
Example
Add comments go:generate mockgen -destination mock_repo/mock_chl_repo.go github.com/cy18cn/octopus/mch/repository ChannelRepository
for interface.
//go:generate mockgen -destination mock_repo/mock_chl_repo.go github.com/cy18cn/octopus/mch/repository ChannelRepository
type ChannelRepository interface {
Get(id uint64) (*model.OpChannel, error)
ListAll() ([]*model.OpChannel, error)
}
Run command go generate
to generate GoMock struct implement ChannelRepository in file mock_repo/mock_chl_repo.go.
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/cy18cn/octopus/mch/repository (interfaces: ChannelRepository)
// Package mock_repository is a generated GoMock package.
package mock_repository
import (
model "github.com/cy18cn/octopus/mch/model"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockChannelRepository is a mock of ChannelRepository interface
type MockChannelRepository struct {
ctrl *gomock.Controller
recorder *MockChannelRepositoryMockRecorder
}
// MockChannelRepositoryMockRecorder is the mock recorder for MockChannelRepository
type MockChannelRepositoryMockRecorder struct {
mock *MockChannelRepository
}
// NewMockChannelRepository creates a new mock instance
func NewMockChannelRepository(ctrl *gomock.Controller) *MockChannelRepository {
mock := &MockChannelRepository{ctrl: ctrl}
mock.recorder = &MockChannelRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockChannelRepository) EXPECT() *MockChannelRepositoryMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockChannelRepository) Get(arg0 uint64) (*model.OpChannel, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
ret0, _ := ret[0].(*model.OpChannel)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockChannelRepositoryMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockChannelRepository)(nil).Get), arg0)
}
// ListAll mocks base method
func (m *MockChannelRepository) ListAll() ([]*model.OpChannel, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListAll")
ret0, _ := ret[0].([]*model.OpChannel)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListAll indicates an expected call of ListAll
func (mr *MockChannelRepositoryMockRecorder) ListAll() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAll", reflect.TypeOf((*MockChannelRepository)(nil).ListAll))
}
4. GoMock
GoMock is a mocking framework for the Go programming language. It integrates well with Go's built-in testing
package, but can be used in other contexts too.
Installation
go get github.com/golang/mock/mockgen
Example
var (
chls = []*model.OpChannel{
{
Id: 1,
ChlType: "wx_app",
ChlName: "微信App支付",
ChlLabel: "微信App支付",
ChlDesc: "微信 App 支付",
ChlLogo: "wx_app.png",
State: 1,
},
{
Id: 2,
ChlType: "wx_jsapi",
ChlName: "微信jsapi支付",
ChlLabel: "微信jsapi支付",
ChlDesc: "微信 jsapi 支付",
ChlLogo: "wx_jsapi.png",
State: 1,
},
{
Id: 3,
ChlType: "wx_mini",
ChlName: "微信mini支付",
ChlLabel: "微信mini支付",
ChlDesc: "微信 mini 支付",
ChlLogo: "wx_mini.png",
State: 1,
},
}
pChls = []*model.OpPayChannel{
{
Id: 1,
AppId: "134567",
ChlId: 1,
PayConfig: "",
State: 1,
},
{
Id: 2,
AppId: "134567",
ChlId: 2,
PayConfig: "",
State: 1,
},
}
app = &model.OpMchApp{
Id: 1,
MchId: "123456",
MchName: "测试",
AppName: "测试",
Appid: "134567",
MchKey: "15234212d4sa4df523s4af",
State: 1,
}
)
func TestPayChlService_pChannelList(t *testing.T) {
Convey("", t, func() {
ctrl := gomock.NewController(t)
chlRepo := mock_repository.NewMockChannelRepository(ctrl)
payChlRepo := mock_repository.NewMockPayChlRepository(ctrl)
mchAppRepo := mock_repository.NewMockMchAppRepository(ctrl)
chlRepo.EXPECT().ListAll().Return(chls, nil)
payChlRepo.EXPECT().FindByAppId("134567").Return(pChls, nil)
pService := &PayChlService{
channelRepo: chlRepo,
payChlRepo: payChlRepo,
mchAppRepo: mchAppRepo,
}
resp, err := pService.pChannelList("134567")
So(err, ShouldBeNil)
So(len(resp), ShouldEqual, 3)
So(resp[2].State, ShouldEqual, 0)
})
}