「GoCN酷Go推荐」golang 单元测试最佳实践

GoCN

共 4418字,需浏览 9分钟

 · 2021-09-27

为什么要进行单元测试?

在没工作之前,说实话没怎么写过单元测试,很多情况下就是一边写代码,一边运行,用 fmt.Println() 打印变量,再稍微复杂一点的也许会用 dlv 去 debug 代码,找出问题。

但是在做公司做大型项目时就会发现,你根本就没办法把项目跑起来,这个时候你只能通过写单元测试去看自己的逻辑对不对。

当然仍旧会出现一个问题,当你的功能中又调用了其他的接口,但这个接口在你当前的环境中是没办法正常调用的,比如数据库连接,文件 I/O。网络I/O 等。这个时候就需要一个 好用的 mock 库了,简单来说就是用 mock 对象模拟依赖项的行为,这里我推荐使用 gomonkey。

从另一个角度讲,为什么需要单元测试呢,因为我们一般项目都有覆盖率的要求,写单测当然是也是为了提高代码覆盖率咯,不然代码都无法提交到 gitlab 上。

gomonkey 入门

安装 gomonkey

go get github.com/agiledragon/gomonkey

gomonkey 常见用法

  • mock 一个函数

  • mock 一个成员方法

  • 其他用法可参考官方文档

业务代码如下:

package mock

import (
 "encoding/json"
 "io/ioutil"
 "net/http"
)

type Request struct {
 Url string
}

type Response struct {
 Result string
}

func exec(args []byte) (res *Response, err error) {
 var w Request
 if err = json.Unmarshal(args, &w); err != nil {
  return nil, err
 }
 if res, err = w.DoAction(w.Url); err != nil {
  return nil, err
 }
 return
}

func (r *Request) DoAction(action string) (resp *Response, err error) {
 var (
  res *http.Response
  b   []byte
 )
 if res, err = http.Get(action); err != nil {
  return nil, err
 }
 if b, err = ioutil.ReadAll(res.Body); err != nil {
  return nil, err
 }
 return &Response{Result: string(b)}, nil
}

test case 如下:

package mock

import (
 "encoding/json"
 "reflect"
 "testing"

 "github.com/agiledragon/gomonkey"
 "github.com/stretchr/testify/assert"
)

func TestExec(t *testing.T) {
 var test = []struct {
  in   []byte
  want *Response
 }{
  {
   in:   []byte("https://gocn.vip/api/v1/count"),
   want: &Response{Result: "666"},
  },
 }
 var r Request
 f := func(t *testing.T) *gomonkey.Patches {
  patches := gomonkey.NewPatches()
  // mock json.Unmarshal()
  patches.ApplyFunc(json.Unmarshal, func(b []byte, d interface{}) error {
   data := d.(*Request)
      // 替换成任何你想要的数据
   (*data).Url = "127.0.0.1/api/v1/count"
   return nil
  })
  // mock 成员方法,注意,成员方法首字母要大写!
  patches.ApplyMethod(reflect.TypeOf(&r), "DoAction"func(_ *Request, _ string) (*Response, error) {
   return &Response{Result: "666"}, nil
  })
  return patches
 }
 t.Run("test"func(t *testing.T) {
  patches := f(t)
  defer patches.Reset()
  for _, v := range test {
   r, err := exec(v.in)
   if !assert.NotNil(t, r) {
    t.Log(err)
    continue
   }
   assert.Equal(t, "666", r.Result)
  }
 })
}
$ go test -gcflags=-l -v -run ./
=== RUN   TestExec
=== RUN   TestExec/test
--- PASS: TestExec (0.00s)
    --- PASS: TestExec/test (0.00s)
PASS
ok      mytest/add      0.018s

当然有时候也为了增加代码覆盖率,需要将if err != nil 等覆盖,可以写多个 f, 如

f1 := func(t *testing.T) *gomonkey.Patches {}

f2 := func(t *testing.T) *gomonkey.Patches {}

最后 t.Run("test1",xxx), t.Run("test2", xxx)即可

参考资料

  • https://github.com/agiledragon/gomonkey/
  • https://github.com/stretchr/testify

《酷Go推荐》招募:


各位Gopher同学,最近我们社区打算推出一个类似GoCN每日新闻的新栏目《酷Go推荐》,主要是每周推荐一个库或者好的项目,然后写一点这个库使用方法或者优点之类的,这样可以真正的帮助到大家能够学习到

新的库,并且知道怎么用。


大概规则和每日新闻类似,如果报名人多的话每个人一个月轮到一次,欢迎大家报名!戳「阅读原文」,即可报名


扫码也可以加入 GoCN 的大家族哟~



浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报