Go 单元测试

618 阅读4分钟

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)
			}
		})
	}
}