乐趣区

关于golang:编写可测试的-Go-代码

测试分类

开发波及的测试次要有以下三种,其余范畴更广的测试如 零碎测试、功能测试 这里就不介绍了。

  • unit test(单元测试):针对程序模块来进行正确性测验的测试工作,程序单元是利用的最小可测试部件。单元测试要求没有内部依赖。
  • integration test(集成测试):也叫组装测试或联结测试。在单元测试的根底上,将所有模块依照设计要求组装成为子系统或零碎,进行集成测试。集成测试通常会接入 db,mq,后端接口等实在依赖。
  • e2e test(端到端测试):从头到尾验证整个软件及其与内部接口的集成。例如应用 Postman 测试 server 接口。

测试前提

  • 模块化:逻辑清晰,各司其职
  • 细粒度:函数式,关注点在输入输出
  • 分层:MVC 边界清晰,而不是带球一条龙
  • 形象:依赖接口而不是具体实现
  • 依赖注入:不便替换为 mock 实现

这些概念看起来比拟 Java,但确实是贵重的工程实际,要学会站在伟人的肩膀上。

测试方法

mock 接口

对接口进行 mock,只关注接口函数的输出和输入,无需实现细节。

实例:gomock

gomock 依据代码中的 interface 接口生产 stub 代码。注入依赖后,应用 EXPECT 提前布局输出和输入,测试调用方行为与预期是否统一。

user.go:

package user

//go:generate mockgen -destination mock_user/mock_user.go github.com/win5do/golang-microservice-demo/docs/sample/gomock/user Index
type Index interface {Get(key string) interface{}}

func GetIndex(in Index, key string) interface{} {
  // business logic
  r := in.Get(key)
  // handle output
  return r
}

mock_user.go:

// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/win5do/golang-microservice-demo/docs/sample/gomock/user (interfaces: Index)

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

import (
    gomock "github.com/golang/mock/gomock"
    reflect "reflect"
)

// MockIndex is a mock of Index interface
type MockIndex struct {
    ctrl     *gomock.Controller
    recorder *MockIndexMockRecorder
}

// MockIndexMockRecorder is the mock recorder for MockIndex
type MockIndexMockRecorder struct {mock *MockIndex}

// NewMockIndex creates a new mock instance
func NewMockIndex(ctrl *gomock.Controller) *MockIndex {mock := &MockIndex{ctrl: ctrl}
    mock.recorder = &MockIndexMockRecorder{mock}
    return mock
}

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

// Get mocks base method
func (m *MockIndex) Get(arg0 string) interface{} {m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "Get", arg0)
    ret0, _ := ret[0].(interface{})
    return ret0
}

// Get indicates an expected call of Get
func (mr *MockIndexMockRecorder) Get(arg0 interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockIndex)(nil).Get), arg0)
}

user_test.go:

func TestGetIndex(t *testing.T) {ctrl := gomock.NewController(t)
    index := mock_user.NewMockIndex(ctrl)
    in := "uid"
    out := "username"

    index.EXPECT().Get(in).Return(out)

    r :=  GetIndex(index, "uid")
    require.Equal(t, out, r)
}

这是一个最最简略的例子,可能看不出来什么。试想一下,如果 GetIndex 逻辑非常复杂,这里 mock 掉依赖调用 Get 的输入输出,就能用单测 100% 笼罩 GetIndex 代码。

示例代码:https://github.com/win5do/go-…

特点:很聪慧的做法,mock 后调用接口齐全通明,筹备好输入输出,通过表格驱动测试,能够笼罩各种边界值。只能在 test 中应用,每次调用前都须要 mock 输入输出,应用略为繁琐。

fake 实现

fake 是指模仿实在依赖做一套简化的实现,屏蔽内部零碎的依赖。

实例:client-go fake client

client-go 依赖于 etcd,其模仿实现 fake-client 包实现了在内存中对资源进行增删改查,与实在依赖 etcd-client 高度一致。

特点:模仿实现编码工作量较大,fake 代码也须要测试来保障正确性,但欠缺后使用方便。

集成测试

间接应用测试环境的实在依赖。当初利用 docker 等容器技术部署依赖十分不便,进行数据初始化后,配置测试代码连贯测试 db 运行集成测试。

应用 docker-compose 本地启动 mysql,mongodb,etcd 等依赖可参考我的另一个我的项目:db-local。

特点:实在依赖跟线上环境一致性高,更容易发现 bug。但比拟轻便,启动慢,测试运行工夫长。

退出移动版