「 Go DEV 」这是 Go 语言的时代
作者 | uuapp
整理 | 猿胖子
出品 | 猿武场(ID:apesarena)
关注公众号并回复数字「 1024 」加入猿武场微信社群

Gin 框架进行单元测试
单元测试(Unit Tests, UT) 是一个优秀项目不可或缺的一部分,大型项目中尤为重要。
mock。
然后,写可测试的代码。高内聚,低耦合
是软件工程的原则,同样,对测试而言,函数/方法写法不同,测试难度也是不一样的。职责单一,参数类型简单,与其他函数耦合度低的函数往往更容易测试。我们经常会说,“这种代码没法测试”,这种时候,就得思考函数的写法可不可以改得更好一些。为了代码可测试而重构是值得的。
接下来将介绍如何使用 Go 语言的标准库 testing
结合 gin
框架进行单元测试。

一个简单的示例项目
该项目创建一个http服务,使用Sonyflake 算法用于生成全局 id,项目目录结构如下:
1exampleUt/
2 routers/
3 |-- router.go // 路由初始化
4 services/
5 |-- globalIdService.go // id生成服务
6 test/
7 |-- genglobalid_test.go // 测试文件
8 main.go
9 go.mod
10 go.sum
测试用例名称一般命名为 Test
加上待测试的方法名。测试用的参数有且只有一个,在这里是
t *testing.T
。模糊测试(1.18支持)的参数是
testing.F`,基准测试(benchmark)的参数是 `testing.B
,TestMain 的参数是*testing.M
类型。
代码实例
main.go
代码如下,初始化路由,启动监听服务
1package main
2
3import "exampleUt/routers"
4
5func main() {
6 router := routers.SetupRouter()
7 router.Run(":8088")
8}
router.go
代码如下,路由初始化,创建一个id生成服务
1package routers
2
3import (
4 "exampleUt/services"
5
6 "github.com/gin-gonic/gin"
7)
8
9func SetupRouter() *gin.Engine {
10 router := gin.Default()
11
12 router.GET("/globalid", services.GlobalIdServiceInstance.GenGlobalId)
13
14 return router
15}
globalIdService.go
代码如下,id生成具体实现方法
1package services
2
3import (
4 "time"
5
6 "github.com/gin-gonic/gin"
7 "github.com/sony/sonyflake"
8)
9
10type GlobalIdService struct {
11}
12
13var (
14 GlobalIdServiceInstance *GlobalIdService
15)
16
17func init() {
18 GlobalIdServiceInstance = &GlobalIdService{}
19
20 //初始化id生成服务,使用sonyflake算法
21 t, _ := time.Parse("2006-01-02", "2019-06-01")
22 settings := sonyflake.Settings{
23 StartTime: t,
24 }
25
26 sf = sonyflake.NewSonyflake(settings)
27 if sf == nil {
28 panic("sonyflake not created")
29 }
30}
31
32var sf *sonyflake.Sonyflake
33
34//生成全局id方法
35func (c *GlobalIdService) GenGlobalId(g *gin.Context) {
36 id, _ := sf.NextID() //生成id
37 g.JSON(200, sonyflake.Decompose(id)) //返回结果
38}
genglobalid_test.go
测试代码如下
1package test
2
3import (
4 "bytes"
5 "encoding/json"
6 "exampleUt/routers"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "net/http/httptest"
11 "testing"
12)
13
14type GlobalId struct {
15 Id uint64 `json:"id"`
16 Machineid uint64 `json:"machine-id"`
17 Msb uint64 `json:"msb"`
18 Sequence uint64 `json:"sequence"`
19 Time uint64 `json:"time"`
20}
21
22//测试函数 t *testing.T 作为入参
23func TestGenGlobalId(t *testing.T) {
24 // 初始化请求地址
25 uri := "/globalid"
26 router := routers.SetupRouter()
27
28 // 发起Get请求
29 rsp := PerformRequest(uri, "GET", nil, router)
30 if rsp.StatusCode != 200 {
31 t.Error("请求响应状态错误:" + rsp.Status) //抛出测试错误信息
32 }
33 // 读取响应body
34 body, _ := ioutil.ReadAll(rsp.Body)
35 fmt.Printf("response:%v\n", string(body))
36
37 var globalid GlobalId
38 err := json.Unmarshal(body, &globalid)
39 if err != nil {
40 t.Error("响应数据格式不正确,body:", string(body)) //抛出测试错误信息
41 }
42
43 if globalid.Id == 0 {
44 t.Error("id 生成错误:", globalid.Id)
45 }
46
47}
48
49//通用http请求
50func PerformRequest(path, method string, param []byte, router http.Handler) *http.Response {
51 // 构造post请求,json数据以请求body的形式传递
52 req := httptest.NewRequest(method, path, bytes.NewReader(param))
53
54 // 初始化响应
55 w := httptest.NewRecorder()
56
57 // 调用相应的handler接口
58 router.ServeHTTP(w, req)
59
60 // 提取响应
61 result := w.Result()
62 return result
63}
进入测试代码目录,运行 go test
,该 package
下所有的测试用例都会被执行。
1$ cd test
2$ go test
3[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
4
5[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
6 - using env: export GIN_MODE=release
7 - using code: gin.SetMode(gin.ReleaseMode)
8
9[GIN-debug] GET /globalid --> exampleUt/services.(*GlobalIdService).GenGlobalId-fm (3 handlers)
10[GIN] 2022/03/31 - 14:12:33 | 200 | 42.25µs | 192.0.2.1 | GET "/globalid"
11response:{"id":149921124212277522,"machine-id":274,"msb":0,"sequence":0,"time":8935995353}
12PASS
13ok exampleUt/test 0.587s
或 go test -v
, -v
参数会显示每个用例的测试结果,另外 -cover
参数可以查看覆盖率。
1$ go test -v -cover
2=== RUN TestGenGlobalId
3[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
4
5[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
6 - using env: export GIN_MODE=release
7 - using code: gin.SetMode(gin.ReleaseMode)
8
9[GIN-debug] GET /globalid --> exampleUt/services.(*GlobalIdService).GenGlobalId-fm (3 handlers)
10[GIN] 2022/03/31 - 15:03:14 | 200 | 115.625µs | 192.0.2.1 | GET "/globalid"
11response:{"id":149926225693901074,"machine-id":274,"msb":0,"sequence":0,"time":8936299425}
12--- PASS: TestGenGlobalId (0.00s)
13PASS
14coverage: [no statements]
15ok exampleUt/test 0.656s
如果有多个测试用例,而你只想运行其中的一个用例,例如 TestGenGlobalId
,可以用 -run
参数指定,该参数支持通配符 *
,和部分正则表达式,例如 ^ 、 $
。
1$ go test -run TestGenGlobalId -v
小结
标准库提供的测试包,还有很多很多好的工具,比如Mock
,后面继续分享吧。
以测试的角度,推行单元测试是不易的,最佳的方式莫过于开发人员,在一定的指引之后,以实际项目出发进行实践,然后自行总结,有针对性进行内部分享,共同学习进步,才能真正的落地。
最后我们会发现,做好单元测试,是一件事半功倍的事情。
如果您喜欢本期教程欢迎点赞、转发、关注 !
更多详情关注本公众号留言获取。
版权声明:本文来自原创,版权归猿武场作者所有。如需转载,请联系作者并注明出处。

注公众号并回复数字「 1024 」加入猿武场微信社群
欢迎加入程序员社群,更多技术摘要等你拿走
社群福利:
1. 行业大牛技术手札,知识点汇总;
2. 求职/招聘信息内推;
4. 人际交往,增强技术宅人际交流;
5. 调节繁杂无趣的闲暇时光;
6. 不定期线上周边於线下技术活动沙龙。





