Golang 有很多优秀的开源框架, 能够帮我们很方便的写出测试代码, 来模拟不同的场景。
gomonkey 是一种很简单的mock框架, 基于打桩函数的方式来进行mock不同的函数调用, 从而实现不同的场景的mock。该代码库地址: https://github.com/agiledragon/gomonkey
我们要测试一个函数, 而这个函数还调用了其他几个函数, 在测试代码中, 我们需要有代码调用上下文才能够使调用子函数成功, 但是通常, 调用上下文有可能很复杂, 没有办法将所有依赖的环境上下文配置好, 比如, 配置文件的设定, 数据库的设定, http/RPC的设定以及第三方库的设定。就是说, 变成上下文是如此的复杂, 几乎无法将所有的上下文配置好。
比较简单的办法就是避免的办法, 就是避免真是的子函数调用, 而是采用打桩的办法, 模拟子函数的调用, 返回不同的值来测试不同的场景。在后面我们给出一些例子来介绍具体的用法。
import(
"fmt"
)
func TestFunc(num1 int, num2 int) int {
res := CompareInt(num1, num2)
fmt.Println("CompareInt return ", res)
return res
}
func CompareInt(num1 int, num2 int) int {
if num1 == num2 {
return 0
}else if num1 > num2 {
return 1
}else {
return -1
}
}
如下的代码, 采用ApplyFunc来测试CompareInt函数:
import (
"fmt"
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"reflect"
"testing"
)
func Test_CompareInt(t *testing.T) {
Convey("CompareInttest", t, func() {
// ApplyFunc 的第一个参数是待测试的函数名字;
// 第二个参数是待测试函数的函数原型
// 这里模拟返回值是0 的情况, 我们还可以返回1或者-1
patch := ApplyFunc(CompareInt, func(_ int, _ int) (int) {
return 0
})
result := TestFunc(1, 1)
So(result, ShouldEqual, 0)
})
fmt.Println("CompareInt test success")
}
上面的例子是最简单情况, TestFunc调用函数CompareInt, 我们测试TestFunc的时候, 要先mock出来CompareInt, 采用ApplyFunc, 输入函数名字和待测试函数原型就可以打桩, 返回我们需要测试的场景。
ApplyFunc对于稍微复杂一点的场景, 例如 interface{} 接口, 就不灵了, 还好gomonkey提供了另外一个函数ApplyMethod来实现这一场景。
import(
"fmt"
)
type Person interface {
Interesting(product string) bool
}
type Student strut {
}
func (s *Student) Interesting(prodName string) bool{
if prodName == "pen" {
return true
} else {
return false
}
}
func testFunc(prodName string) bool{
st := &Student{}
result := st.Interesting(prodName)
if false == result {
fmt.Println("Student like product ", prodName)
}
return result
}
ApplyMethod的用法和ApplyFunc的用法类似, 第一个参数代表struct的类型, 第二个参数是待测试函数的名字的字符串, 第三个参数是待测试函数的原型。
import (
"fmt"
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"reflect"
"testing"
)
func Test_testFunc(t *testing.T) {
stu := &Student{}
Convey("testFunc success", t, func() {
patch := ApplyMethod(reflect.TypeOf(stu), "testFunc", func(_ string) (bool) {
return true
})
result := TestFunc("pen")
So(result, ShouldEqual, true)
})
Convey("testFunc fail", t, func() {
patch := ApplyMethod(reflect.TypeOf(stu), "testFunc", func(_ string) (bool) {
return false
})
result := TestFunc("book")
So(result, ShouldEqual, false)
})
fmt.Println("CompareInt test success")
}
上面的例子, 函数testFunc传入一个字符串, 如果是“pen”就返回true, 否则false, 我们经过ApplyMethod来mock interface。
Http handler是golang代码里面很重要的一部分, 它是http调用的入口函数, 在golang的官方代码包里面包含了httptest, 它可以很方便的帮我们测试相关的代码。
import(
"net/http"
"encoding/json"
)
type Person struct {
Name string
Age int
}
http.HandleFunc("/get_host_info", GetHostInfoHandler)
func GetHostInfoHandler(w http.ResponseWriter, r *http.Request) {
bytes, _:= ioutil.ReadAll(r.Body)
if len(bytes) == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
request := &Person{}
err = json.Unmarshal(bytes, &request)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
fmt.Println("Request paramter: %+v", request)
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"Code": 0, "Message":"Success"}`)
}
httptest主要就是mock出来一个http.Request, 以及一个http.ResponseWriter, 对于传入的参数以及返回给调用者的response, 可以根据所测试的函数进行适配。
import (
"fmt"
. "github.com/agiledragon/gomonkey"
. "github.com/smartystreets/goconvey/convey"
"reflect"
"testing"
)
func Test_GetHostInfoHandler(t *testing.T) {
reqData := &Person{
Name: "baijiwei",
Age:30,
}
reqBody, _ := json.Marshal(reqData)
rw := httptest.NewRecorder()
req, err := http.NewRequest("POST", "/get_host_info", bytes.NewReader(reqBody))
if err != nil {
t.Fatal(err)
}
GetHostInfoHandler(rw, req)
response := &Person{}
err = json.Unmarshal(rw.Body.Bytes(), response)
if err != nil {
fmt.Println("unmarshal body fail")
}
}