Golang Test

这篇博客介绍了如何在Golang中进行测试,包括使用GoConvey进行测试,利用sqlmock模拟数据库行为,通过go generate自动生成代码,以及GoMock的使用方法。详细讲解了每个工具的安装、示例和特点。
摘要由CSDN通过智能技术生成

目录

1. GoConvey

Installation

Example

2. Mock DB

Install

Example

3. Generating code

Example

4. GoMock

Installation

Example


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)
    })
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值