Home Go单元测试 Unit Test For Go
Post
Cancel

Go单元测试 Unit Test For Go

单元测试,一个不断被强调,但又不断被忽略的话题,从一名码农成长为一名优秀的工程师,单元测试,是必不可少的技能。单元测试是在所有测试环节中最先完成的,在我们写代码的过程中就需要写单元测试,也可能在未写代码前先写测试。个人认为编码能力,代码评审和单元测试是一个研发同学必备的三大重要技能。

Go常用测试框架

框架名称特征优点缺点备注
Testingtable-driver测试
支持基准测试
Go原生
简单易用
不支持mock
不支持assert
testing包文档:
cngolib.com/testing.htm
Testifytable-driver测试

支持mock
支持assert

兼容go test
支持assert,功能强大易用
支持mock
兼容go test
社区活跃度高
mock能力较弱官方文档:pkg.go.dev/github.com/s
GoConvey支持assert

支持web界面
支持colorized输出
支持测试用例层级嵌套
兼容go test
支持web界面
支持colorized输出
支持测试用例层级嵌套
不支持mock
社区不太活跃
项目地址:
github.com/smartystreet

安装:go get github.com/smartystreet
Ginkgo支持基准测试
支持并发测试
兼容go test
支持并发测试手册:ginkgo.wiki/
安装:
go get github.com/onsi/ginkgo/

Mock

在写单测过程中,常会遇到有些不容易构造或者不容易获取的对象,这时候我们要创建一个虚拟的对象以便测试。

常用的Mock可以归纳为两类:

打桩Stub

代表框架:GoMonkey、monkey、GoStub

主要通过打补丁(Patch)的方式替换函数、方法、变量等等

	monkey.PatchInstanceMethod(reflect.TypeOf(personService), "GetPersonById", func(_ *PersonServiceImpl, _ string) (*Person, error) {
		return &Person{
			Name: "test_name",
		}, nil
	})

依赖注入+GoMock

这种方法要求业务代码必须以“依赖倒置”的方式实现。关于什么是“依赖倒置原则”,参考官方的定义:

  1. 高层模块不应该依赖底层模块,两个都应该依赖抽象。
  2. 抽象不应该依赖细节,细节应该依赖抽象。“抽象”是指接口(interface)或者抽象类(abstract class),“细节”指的是实现(struct或者class)。

举例来说,比如在业务层我们会定义一些接口,如获取用户信息:

type UserStorager interface {
	GetUserInfo(context.Context, *UserParam) (User, error)
}

对于这些接口使用gomock进行模拟,通过mockgen -source=user.go -destination=user_mock.go 可自动生成接口应的mock实现

// Code generated by MockGen. DO NOT EDIT.
// Source: user.go

// Package mock_user is a generated GoMock package.
package mock_user

import (
	context "context"
	reflect "reflect"

	gomock "github.com/golang/mock/gomock"
	ibasic "icode.baidu.com/baidu/bfe-private-cloud/control-api/api-server/module/ibasic"
)

// MockUserStorager is a mock of UserStorager interface.
type MockUserStorager struct {
	ctrl     *gomock.Controller
	recorder *MockUserStoragerMockRecorder
}

// MockUserStoragerMockRecorder is the mock recorder for MockUserStorager.
type MockUserStoragerMockRecorder struct {
	mock *MockUserStorager
}

// NewMockUserStorager creates a new mock instance.
func NewMockUserStorager(ctrl *gomock.Controller) *MockUserStorager {
	mock := &MockUserStorager{ctrl: ctrl}
	mock.recorder = &MockUserStoragerMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUserStorager) EXPECT() *MockUserStoragerMockRecorder {
	return m.recorder
}

// GetUserInfo mocks base method.
func (m *MockUserStorager) GetUserInfo(arg0 context.Context, arg1 *ibasic.UserParam) (ibasic.User, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "GetUserInfo", arg0, arg1)
	ret0, _ := ret[0].(ibasic.User)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// GetUserInfo indicates an expected call of GetUserInfo.
func (mr *MockUserStoragerMockRecorder) GetUserInfo(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserInfo", reflect.TypeOf((*MockUserStorager)(nil).GetUserInfo), arg0, arg1)
}
This post is licensed under CC BY 4.0 by the author.
Contents