本文中出现的代码都是基于goframe框架,不熟悉框架建议跳转:
goframe文档
测试分类 #
- 单元测试
- 集成测试
- 系统测试
- 功能测试
- 性能测试
- 安全测试
- 兼容测试
golang单元测试 #
表格驱动测试 #
使用工具:官方测试工具
go test
- 所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。
- 单元测试函数:函数名前缀为Test
- 基准测试函数:函数名前缀为Benchmark
- 示例函数:函数名前缀为Example
func Test_sMock_GetTest(t *testing.T) {
// 定义匿名结构体
type args struct {
ctx context.Context
key string
}
tests := []struct {
name string
args args
want string
}{
// 测试表格
{
name: "Test_Mock_GetTest",
args: args{
ctx: context.Background(),
key: "test",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &sMock{}
if got := s.GetTest(tt.args.ctx, tt.args.key); got != tt.want {
t.Errorf("GetTest() = %v, want %v", got, tt.want)
}
})
}
}
并行测试 #
func Test_sMock_GetTest(t *testing.T) {
// 定义此测试能够与其他测试并行
t.Parallel()
// 定义匿名结构题
type args struct {
ctx context.Context
key string
}
tests := []struct {
name string
args args
want string
}{
// 测试表格
{
name: "Test_Mock_GetTest",
args: args{
ctx: context.Background(),
key: "test",
},
},
}
for _, tt := range tests {
// 避免多个gorountine中使用了相同的变量
tt := tt
t.Run(tt.name, func(t *testing.T) {
// 定义每个测试用例能够并行
t.Parallel()
s := &sMock{}
if got := s.GetTest(tt.args.ctx, tt.args.key); got != tt.want {
t.Errorf("GetTest() = %v, want %v", got, tt.want)
}
})
}
}
集成测试-go mock #
使用工具:官方测试工具
go mock
- 针对不同接口之间的调用,可以采用mock的方式来屏蔽其他接口的调用返回,常用于对第三方的调用。
- 本质是利用golang interface 来包含调用的接口,来模拟其返回。
- 缺点:
- 需要将包含了调用接口的具体对象传递给代测试函数。
- 只适合用来调试不同logic之间互相调用的情况。
步骤 #
- 下载mock工具。
go install github.com/golang/mock/[email protected]
- 根据需要mock的函数准备golang的接口和实现。
举例说明
- 现在存在两个函数/方法,其中
GetTest
需要调用Get
。 - 而在测试的时候,我需要对
Get
进行模拟。
接口如下定义:
// service, you can use "gf gen service" generated
// ================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================
package service
import (
"context"
)
type (
IMock interface {
Get(ctx context.Context, key string) string
GetTest(ctx context.Context, key string) string
}
)
var (
localMock IMock
)
func Mock() IMock {
if localMock == nil {
panic("implement not found for interface IMock, forgot register?")
}
return localMock
}
func RegisterMock(i IMock) {
localMock = i
}
具体实现如下:
// logic
type sMock struct{}
func New() *sMock {
return &sMock{}
}
func init() {
service.RegisterMock(New())
}
func (s *sMock) Get(ctx context.Context, key string) string {
return key
}
func (s *sMock) GetTest(ctx context.Context, key string) string {
// 关键点2,使用go mock对象,其中 service.Mock()对应上述函数
return service.Mock().Get(ctx, key)
}
- 生成mock文件。
- source: 源文件路径
- destination: 目标文件路径
- package: 包名
mockgen -source=internal/service/mock.go -destination=internal/mock/mock_mock.go -package=mock
mock文件示例:
// Code generated by MockGen. DO NOT EDIT.
// Source: internal/service/mock.go
// Package mock is a generated GoMock package.
// MockIMock is a mock of IMock interface.
type MockIMock struct {
ctrl *gomock.Controller
recorder *MockIMockMockRecorder
}
// MockIMockMockRecorder is the mock recorder for MockIMock.
type MockIMockMockRecorder struct {
mock *MockIMock
}
// NewMockIMock creates a new mock instance.
func NewMockIMock(ctrl *gomock.Controller) *MockIMock {
mock := &MockIMock{ctrl: ctrl}
mock.recorder = &MockIMockMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIMock) EXPECT() *MockIMockMockRecorder {
return m.recorder
}
// Get mocks base method.
func (m *MockIMock) Get(ctx context.Context, key string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", ctx, key)
ret0, _ := ret[0].(string)
return ret0
}
// 关键点3: 实际调用的Get函数
// Get indicates an expected call of Get.
func (mr *MockIMockMockRecorder) Get(ctx, key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockIMock)(nil).Get), ctx, key)
}
// GetTest mocks base method.
func (m *MockIMock) GetTest(ctx context.Context, key string) string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTest", ctx, key)
ret0, _ := ret[0].(string)
return ret0
}
// GetTest indicates an expected call of GetTest.
func (mr *MockIMockMockRecorder) GetTest(ctx, key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTest", reflect.TypeOf((*MockIMock)(nil).GetTest), ctx, key)
}
- 准备测试文件
// mock
func Test_sMock_GetTest(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := mock.NewMockIMock(ctrl)
// 关键点1,传递go mock对象
service.RegisterMock(m)
// 打桩,假如传递test,则对应的接口传递test1111出来
m.EXPECT().Get(context.Background(), gomock.Eq("test")).Return("test1111").Times(1)
type args struct {
ctx context.Context
key string
}
tests := []struct {
name string
args args
want string
}{
{
name: "Test_Mock_GetTest",
args: args{
ctx: context.Background(),
key: "test",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &sMock{}
if got := s.GetTest(tt.args.ctx, tt.args.key); got != tt.want {
t.Errorf("GetTest() = %v, want %v", got, tt.want)
}
})
}
}
集成测试-http #
- 利用postman等工具。
- 如下所示,建立测试。
func Test_Params_Parse_Validation(t *testing.T) {
// 定义请求体
type RegisterReq struct {
Name string `p:"username" v:"required|length:6,30#请输入账号|账号长度为:min到:max位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}
// 准备请求路由定义
p, _ := ports.PopRand()
s := g.Server(p)
s.BindHandler("/parse", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
r.Response.Write(err)
} else {
r.Response.Write("ok")
}
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
// 开启测试
gtest.C(t, func(t *gtest.T) {
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
client := g.Client()
client.SetPrefix(prefix)
t.Assert(client.GetContent("/parse"), `请输入账号; 账号长度为6到30位; 请输入密码; 密码长度不够; 请确认密码; 密码长度不够; 两次密码不一致`)
t.Assert(client.GetContent("/parse?name=john11&password1=123456&password2=123"), `密码长度不够; 两次密码不一致`)
t.Assert(client.GetContent("/parse?name=john&password1=123456&password2=123456"), `账号长度为6到30位`)
t.Assert(client.GetContent("/parse?name=john11&password1=123456&password2=123456"), `ok`)
})
}