后端与go测试 | 青训营笔记

109 阅读3分钟

后端与go测试 | 青训营笔记

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天。

今天笔记记录一下go的单元测试和基准测试

1.单元测试

  • 单元测试是软件工程中降低开发成本,提高软件质量常用方式之一,单元测试是一项由开发人员或者测试人员来对程序模块的正确性进行检验测试的工作,用于检查被测试代码的功能是否正确,养成单元测试的习惯,不但可以提高代码的质量,还可以提升自己的编程和技巧。
  • go test命令会遍历所有的*_test.go文件中符合Test前缀的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
  • 对于web开发,网络测试推荐Go标准库net/http/httptest,数据库测试推荐"github.com/DATA-DOG/go-sqlmock"

1. 例子

//一个结合表格驱动测试和网络测试的用户登录测试的例子func UserLogin(c *gin.Context) {
    u := tools.ParseUser(c)
    fmt.Println(u)
    loginUser, err := userService.Login(u)
    if err != nil {
        fmt.Println("err:", err.Error())
        tools.Error(c, -1, "登录失败", "")
    } else {
        tools.Success(c, 0, "登录成功", loginUser)
    }
}
​
//测试
func TestUserLogin(t *testing.T) {
    tests := []struct {
        name   string
        param  string
        expect string
    }{
        {"good case", `{"user_name": "xy1111","pass_word":"1234567"}`, "登录成功"},
        {"bad case", `{"user_name": "a","pass_word":"1234567"}`, "登录失败"},
    }
    r := gin.Default()
    dao.InitDB()
    r.POST("/login", UserLogin)
​
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // httptest 这个包是为了mock一个HTTP请求
            // 参数分别是请求方法,请求URL,请求Body
            // Body只能使用io.Reader
            req := httptest.NewRequest(
                "POST",                      // 请求方法
                "/login",                    // 请求URL
                strings.NewReader(tt.param), // 请求参数
            )
​
            // mock一个响应记录器
            w := httptest.NewRecorder()
​
            // 让server端处理mock请求并记录返回的响应内容
            r.ServeHTTP(w, req)
​
            // 校验状态码是否符合预期
            assert.Equal(t, http.StatusOK, w.Code)
​
            // 解析并检验响应内容是否复合预期
            var resp model.Result
            err := json.Unmarshal([]byte(w.Body.String()), &resp)
            assert.Nil(t, err)
            assert.Equal(t, tt.expect, resp.Msg)
        })
    }
}

2.命令参数

go test -v

go test只能看到这个文件中一次得到所有测试函数是否通过

加上参数-能够看到各个函数的情况

go test-run=[正则表达式] -v

-run参数的作用,它对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行。

go test -cover

Go提供内置功能来检查你的代码覆盖率,课上说50%~60%是一般情况,80%成本高

go test -cover -coverprofile=c.out

Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。

上面的命令会将覆盖率相关的信息输出到当前文件夹下面的c.out文件中。

然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

go test -short

这个可以进入使用了testing.Short()函数的分支

2.基准测试

  • 用于测试当前性能,便于对比优化
  • 在 _test.go 结尾的测试文件中,对测试函数添加Benchmark前缀,通过 go test 命令,加上 -bench 标志来执行。
  • go test 命令默认不执行 benchmark 测试,需要加上 -bench 参数,该参数支持正则表达式,只有匹配到的测试用例才会执行,使用 . 则运行所有测试用例

1. 例子

func BenchmarkHello(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

基准函数会运行目标代码 b.N 次。在基准执行期间,程序会自动调整 b.N 直到基准测试函数持续足够长的时间。输出结果形如:

 go test -bench='Hello' -benchmem
goos: windows
goarch: amd64
pkg: leetcode/test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkHello-12       25629633                43.71 ns/op            5 B/op          1 allocs/op
PASS
ok      leetcode/test   1.249s
​

意味着循环执行了 25629633次,每次循环花费 43.71纳秒 (ns),每次申请5B的内存,每次执行申请了1次内存

参考

testing - 基准测试 · Go语言标准库 (studygolang.com)

Go benchmark 详解 - YahuiAn - 博客园 (cnblogs.com)