项目组长让我写单元测试,不会写,学习如下
-
好像写的很好,但是一开始没看懂,学习完如下面两篇文章就懂了使用Golang的官方mock工具–gomock
-
所以找到如下这篇文章,写的通俗易懂使用 Gomock 进行单元测试
安装
go get github.com/golang/mock/gomock
go get github.com/golang/mock/mockgen
例子摘抄在这里,以便复习使用
tree .
.
── mock
└── mock
└── male_mock.go
└── person
└── male.go
└── user
└── user.go
└── user_test.go
这个文件是生成的,mockgen -source=./person/male.go -destination=./mock/male_mock.go -package=mock
male_mock.go
// Code generated by MockGen. DO NOT EDIT.
// Source: ./person/male.go
// Package mock is a generated GoMock package.
package mock
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockMale is a mock of Male interface.
type MockMale struct {
ctrl *gomock.Controller
recorder *MockMaleMockRecorder
}
// MockMaleMockRecorder is the mock recorder for MockMale.
type MockMaleMockRecorder struct {
mock *MockMale
}
...省略...
male.go一个接口方法
package person
type Male interface {
Get(id int64) error //这个接口并未实现呢,功能是获取一个id
}
这里是主要的业务代码,调用了上面的接口
package user
import "***/mock/person"
type User struct {
Person person.Male
}
func NewUser(p person.Male) *User {
return &User{Person: p}
}
func (u *User) GetUserInfo(id int64) error {
return u.Person.Get(id) //调接口
}
user_test.go 测试文件
package user
import (
"github.com/golang/mock/gomock"
"newgit.op.ksyun.com/kai/service-op-console/mock/mock"
"testing"
)
/*
为啥要用mock呢,因为调用过程是user -> GetUserInfo -> Get(male)的调用过程很长,我的Get接口不可用,但是
我还想测user -> GetUserInfo -> Get(male) 整个过程,这时候我就可以伪造一个Get(male),就可以依靠testing
完成这个测试。
*/
//mockgen -source=./person/male.go -destination=./mock/male_mock.go -package=mock
func TestUser_GetUserInfo(t *testing.T) {
ctl := gomock.NewController(t) //开启一个mock
defer ctl.Finish() //关闭mock
var id int64 = 1
mockMale := mock.NewMockMale(ctl) //创建一个新的 mock 实例
gomock.InOrder( //声明给定的调用应按顺序进行
mockMale.EXPECT().Get(id).Return(nil), //模拟Get,设置入参并调用,设置返回值
//mockMale.EXPECT().Get(id).Return(errors.New("error")), //可以设置返回值
)
user := NewUser(mockMale) //创建User实例,注入mock对象,所以下方调用的是我们事先模拟好的 mock 方法
err := user.GetUserInfo(id)
if err != nil {
t.Errorf("user.GetUserInfo err: %v", err)
}
}
- 还有一个就是testing,B站看的视频,文档地址李文周老师的:Go语言基础之单元测试
例子摘抄在这里,以便复习使用
tree .
.
── split
└── split.go
└── split_test.go
被测试代码
package split
import "strings"
// split package with a single split function.
// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) (result []string) {
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
s = s[i+1:]
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
//sep为多个字符
func MoreSplit(s, sep string) (result []string) {
i := strings.Index(s, sep)
for i > -1 {
result = append(result, s[:i])
s = s[i+len(sep):] // 这里使用len(sep)获取sep的长度
i = strings.Index(s, sep)
}
result = append(result, s)
return
}
测试代码
package split
import (
"fmt"
"reflect"
"testing"
)
//简单的一个测试
func TestSplit(t *testing.T) { // 测试函数名必须以Test开头,必须接收一个*testing.T类型参数
got := Split("a:b:c", ":") // 程序输出的结果
want := []string{"a", "b", "c"} // 期望的结果
if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较
t.Errorf("excepted:%v, got:%v", want, got) // 测试失败输出错误提示
}
}
//另一个测试
func TestMoreSplit(t *testing.T) {
got := MoreSplit("abcd", "bc")
want := []string{"a", "d"}
if !reflect.DeepEqual(want, got) {
t.Errorf("excepted:%v, got:%v", want, got)
}
}
//组测试
func TestSplitList(t *testing.T) {
// 定义一个测试用例类型
type test struct {
input string
sep string
want []string
}
// 定义一个存储测试用例的切片
tests := []test{
{
input: "a:b:c",
sep: ":",
want: []string{"a", "b", "c"},
},
{
input: "a:b:c",
sep: ",",
want: []string{"a:b:c"},
},
{
input: "abcd",
sep: "bc",
want: []string{"a", "d"},
},
{
input: "沙河有沙又有河",
sep: "沙",
want: []string{"河有", "又有河"},
},
}
// 遍历切片,逐一执行测试用例
for _, tt := range tests {
got := Split(tt.input, tt.sep)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("excepted:%#v, got:%#v", tt.want, got)
}
}
}
//如果测试用例比较多的时候,我们是没办法一眼看出来组测试中具体是哪个测试用例失败了。增加自测试
func TestSubSplit(t *testing.T) {
type test struct { // 定义test结构体
input string
sep string
want []string
}
tests := map[string]test{ // 测试用例使用map存储
"case 1": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
"case 2": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
"case 3": {input: "abcd", sep: "bc", want: []string{"a", "d"}}, //测试失败
"case 4": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}}, //测试失败
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) { // 使用t.Run()执行子测试
got := Split(tt.input, tt.sep)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("excepted:%#v, got:%#v", tt.want, got)
}
})
}
}
//子测试的Setup与Teardown
// 测试集的Setup与Teardown
func setupTestCase(t *testing.T) func(t *testing.T) {
var a, b int
a = 1
b = 2
c := a + b
fmt.Println(c)
t.Log("如有需要在此执行:测试之前的setup")
return func(t *testing.T) {
c = b-a
fmt.Println(c)
t.Log("如有需要在此执行:测试之后的teardown")
}
}
// 子测试的Setup与Teardown
func setupSubTest(t *testing.T) func(t *testing.T) {
t.Log("如有需要在此执行:子测试之前的setup")
return func(t *testing.T) {
t.Log("如有需要在此执行:子测试之后的teardown")
}
}
func TestListSplit(t *testing.T) {
type test struct { // 定义test结构体
input string
sep string
want []string
}
tests := map[string]test{ // 测试用例使用map存储
"simple": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
"wrong sep": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
"more sep": {input: "abcd", sep: "bc", want: []string{"a", "d"}},
"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"", "河有", "又有河"}},
}
teardownTestCase := setupTestCase(t) // 测试之前执行setup操作
defer teardownTestCase(t) // 测试之后执行testdoen操作
for name, tc := range tests {
t.Run(name, func(t *testing.T) { // 使用t.Run()执行子测试
//teardownSubTest := setupSubTest(t) // 子测试之前执行setup操作
//defer teardownSubTest(t) // 测试之后执行testdoen操作
got := Split(tc.input, tc.sep)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("excepted:%#v, got:%#v", tc.want, got)
}
})
}
}