Go 单元测试
单元测试是开发中非常重要的环节之一。
在开发完成后、迭代改动后、代码改动后、添加功能后......快速的帮助开发人员完成代码的测试。
一.目标
不区分语言,一个函数、一个方法、一句SQL、一段业务代码,都可以成为单元测试的目标。
单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。
我们知道,在开发时越早发现BUG,就能节省更多的时间,降低更多的风险。
二.目的
单元测试一般由输入数据和预期输出组成。
输入数据:Request,输入的请求参数。
预期输出:Response,预期的返回参数。
输入数据和预期输出都是由我们事先写好的,而单元测试的目的,就是:
检测目标代码,在拿到我们的输入数据后,是否能返回和我们预期输出一样的内容。
三.Go中的单元测试
Go 标准库中有一个叫做
testing的测试框架, 可以用于单元测试和性能测试,它是和命令go test集成使用的。 测试文件是以后缀_test.go命名的,通常和被测试的文件放在同一个包中。
1.简单Demo
目录结构
util
┃━━━ ltMath.go
┗━━━ ltMath_test.go
ltMath.go
package 单元测试
import "errors"
/**
* @Description: 获得两数相除之商
* @param a 参数1
* @param b 参数2
* @return int 参数1与参数2之商
*/
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为0")
}
return a / b, nil
}
ltMath_test.go
package 单元测试
import "testing"
/**
* @Description: Divide单元测试
* @param t
*/
func TestItMathDivide(t *testing.T) {
//输入数据
req1, req2 := 4, 0
//预期输出
res := 2
result, err := Divide(req1, req2)
if err != nil || res != result {
t.Error("Divide failed, err:", err)
}
}
运行,输出结果
输入数据 4,2
=== RUN TestItMathDivide
--- PASS: TestItMathDivide (0.00s)
PASS
输入数据 4,0
=== RUN TestItMathDivide
TestItMathDivide: ltMath_test.go:19: Divide failed, err: 除数不能为0
--- FAIL: TestItMathDivide (0.00s)
FAIL
2.多用例测试
当需要测试的输入数据组合比较多的时候,可以先根据需求,创建测试用例数组。
测试代码
/**
* @Description: Divide单元测试2
* @param t
*/
func TestItMathDivideMulti(t *testing.T) {
// 定义请求响应参数
type args struct {
req1 int
req2 int
res int
}
// 定义测试用例
tests := []struct {
// 测试用例姓名
name string
// 请求响应参数
args args
// 判断条件
wantErr bool
}{
{
name: "正常测试",
args: args{
// 定义 输入数据
req1: 1,
req2: 1,
// 定义 预期输出
res: 1,
},
wantErr: true,
},{
name: "除数为0",
args: args{
// 定义 输入数据
req1: 1,
req2: 0,
// 定义 预期输出
res: 0,
},
wantErr: true,
},{
name: "正常测试2",
args: args{
// 定义 输入数据
req1: 100,
req2: 10,
// 定义 预期输出
res: 10,
},
wantErr: true,
},
}
// 遍历测试用例
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 调用Handler,传入参数
result, err := Divide(tt.args.req1, tt.args.req2)
// 判断是否符合预期
if (tt.args.res == result) != tt.wantErr {
t.Errorf("TestItMathDivideMulti() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
运行结果
=== RUN TestItMathDivideMulti
=== RUN TestItMathDivideMulti/正常测试
=== RUN TestItMathDivideMulti/除数为0
=== RUN TestItMathDivideMulti/正常测试2
--- PASS: TestItMathDivideMulti (0.00s)
--- PASS: TestItMathDivideMulti/正常测试 (0.00s)
--- PASS: TestItMathDivideMulti/除数为0 (0.00s)
--- PASS: TestItMathDivideMulti/正常测试2 (0.00s)
PASS
3.业务单元测试
一般情况下,我们会在业务层写一些单元测试,保证接口的可用性和健壮性。
不同于普通的函数测试,这类测试中,我们需要连接Redis\MySQL等等。
// 这些参数,测试用例中,可以共用
var yaml = &ConfYaml{
DB: DBConf{
Username: "",
Password: "",
Host: "",
DBName: "",
MaxIdle: 5,
MaxOpen: 10,
},
}
const (
CodeSucc = 0
)
func TestUserSysAgent_AddUser(t *testing.T) {
// 服务数据库等初始化
agent, err := NewUserSysAgent(yaml)
if err != nil {
fmt.Printf("AddUser NewUserSysAgent err:%s\n", err.Error())
}
// 测试后关闭连接
defer agent.Close()
type fields struct {
agent *UserSysAgent
}
// 定义请求响应参数
type args struct {
req *AddUserReq
rsp *AddUserRes
}
// 定义测试用例
tests := []struct {
// 测试用例姓名
name string
// 服务
fields fields
// 请求响应参数
args args
// 判断条件
wantErr bool
}{
{
name: "Save draft",
fields: fields{
// 使用默认服务
agent: agent,
},
args: args{
// 定义请求参数
req: &AddUserReq{
username: "test1",
},
rsp: &AddUserRes{},
},
wantErr: false,
},
}
// 遍历测试用例
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 调用Handler,传入参数
err := tt.fields.agent.AddUser(tt.args.req, tt.args.rsp)
fmt.Printf("AddUser(), object:%v, param:%v.\n", tt.args.req, tt.args.rsp)
// 判断是否符合预期
if (tt.args.rsp.Code != CodeSucc) != tt.wantErr {
t.Errorf("AddUser() error = %v, wantErr %v, ret = %v", err, tt.wantErr, tt.args.rsp.Code)
}
})
}
}