Skip to main content

golang测试

·752 words·4 mins
😈long && 😻liang
Author
😈long && 😻liang
A IT worker with PHP/GO as the main technology stack
work - This article is part of a series.
Part 2: This Article

本文中出现的代码都是基于goframe框架,不熟悉框架建议跳转:
goframe文档

测试分类
#


  1. 单元测试
  2. 集成测试
  3. 系统测试
    1. 功能测试
    2. 性能测试
    3. 安全测试
    4. 兼容测试

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
  1. 针对不同接口之间的调用,可以采用mock的方式来屏蔽其他接口的调用返回,常用于对第三方的调用。
  2. 本质是利用golang interface 来包含调用的接口,来模拟其返回。
  3. 缺点:
    1. 需要将包含了调用接口的具体对象传递给代测试函数。
    2. 只适合用来调试不同logic之间互相调用的情况。

步骤
#


  1. 下载mock工具。
go install github.com/golang/mock/[email protected]
  1. 根据需要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)  
}
  1. 生成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)
}
  1. 准备测试文件
// 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
#


  1. 利用postman等工具。
  2. 如下所示,建立测试。
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`)
	})
}

work - This article is part of a series.
Part 2: This Article