「GoCN酷Go推薦」golang 單元測試最佳實(shí)踐
為什么要進(jìn)行單元測試?
在沒工作之前,說實(shí)話沒怎么寫過單元測試,很多情況下就是一邊寫代碼,一邊運(yùn)行,用 fmt.Println() 打印變量,再稍微復(fù)雜一點(diǎn)的也許會用 dlv 去 debug 代碼,找出問題。
但是在做公司做大型項(xiàng)目時就會發(fā)現(xiàn),你根本就沒辦法把項(xiàng)目跑起來,這個時候你只能通過寫單元測試去看自己的邏輯對不對。
當(dāng)然仍舊會出現(xiàn)一個問題,當(dāng)你的功能中又調(diào)用了其他的接口,但這個接口在你當(dāng)前的環(huán)境中是沒辦法正常調(diào)用的,比如數(shù)據(jù)庫連接,文件 I/O。網(wǎng)絡(luò)I/O 等。這個時候就需要一個 好用的 mock 庫了,簡單來說就是用 mock 對象模擬依賴項(xiàng)的行為,這里我推薦使用 gomonkey。
從另一個角度講,為什么需要單元測試呢,因?yàn)槲覀円话沩?xiàng)目都有覆蓋率的要求,寫單測當(dāng)然是也是為了提高代碼覆蓋率咯,不然代碼都無法提交到 gitlab 上。
gomonkey 入門
安裝 gomonkey
go get github.com/agiledragon/gomonkey
gomonkey 常見用法
mock 一個函數(shù)
mock 一個成員方法
其他用法可參考官方文檔
業(yè)務(wù)代碼如下:
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)
// 替換成任何你想要的數(shù)據(jù)
(*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
當(dāng)然有時候也為了增加代碼覆蓋率,需要將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同學(xué),最近我們社區(qū)打算推出一個類似GoCN每日新聞的新欄目《酷Go推薦》,主要是每周推薦一個庫或者好的項(xiàng)目,然后寫一點(diǎn)這個庫使用方法或者優(yōu)點(diǎn)之類的,這樣可以真正的幫助到大家能夠?qū)W習(xí)到
新的庫,并且知道怎么用。
大概規(guī)則和每日新聞類似,如果報(bào)名人多的話每個人一個月輪到一次,歡迎大家報(bào)名!戳「閱讀原文」,即可報(bào)名
掃碼也可以加入 GoCN 的大家族喲~
