wtysos11 / blogWiki

Use to store public paper and organize them.
17 stars 4 forks source link

golang单元测试与mock #202

Open wtysos11 opened 3 years ago

wtysos11 commented 3 years ago

来源于KM社区的两篇相关文章,因为公司规定就没有办法放上来了。

代码覆盖率

go自带的单元测试工具可以计算代码覆盖率,以下面这个很简单的文件为例 main.go

package main

import (
    "encoding/json"
    "fmt"
)

type Test2 struct{
    C int
}

type Test1 struct{
    A int
    B Test2
}

func Add(a,b int) int{
    return a+b
}
func Reduce(a,b int) int{
    if a==0{
        return a
    }
    return a-b
}

func main(){
    //s := Test1{
    //  A:5,
    //  B: Test2{
    //      C:6,
    //  },
    //}
    //st,_  := json.Marshal(s)
    //fmt.Println(string(st))
    st := []byte("{\"A\":5}")
    real := &Test1{}
    err := json.Unmarshal(st,real)
    if err != nil{
        fmt.Println(err)
    }else{
        t := Test2{}
        fmt.Println(real)
        if real.B == t{
            fmt.Println("equal")
        }
    }
}

test.go

package main

import "testing"

func TestAdd(t *testing.T) {
    type args struct {
        a int
        b int
    }
    tests := []struct {
        name string
        args args
        want int
    }{
        {"test1",args{1,2},3},
        {"test1",args{2,2},4},
        {"test1",args{3,2},5},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.args.a, tt.args.b); got != tt.want {
                t.Errorf("Add() = %v, want %v", got, tt.want)
            }
        })
    }
}

func TestReduce(t *testing.T) {
    type args struct {
        a int
        b int
    }
    tests := []struct {
        name string
        args args
        want int
    }{
        {"test1",args{1,2},-1},
        {"test1",args{2,2},0},
        {"test1",args{3,2},1},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Reduce(tt.args.a, tt.args.b); got != tt.want {
                t.Errorf("Reduce() = %v, want %v", got, tt.want)
            }
        })
    }
}

单元测试

Question:哪些代码需要单元测试

当然,覆盖率并不是越高越好,因为很多代码的单元测试性价比并不高。如果以x轴代表投入成本(即开发者的时间和精力),y轴为单元测试带来的收益,那么左上角应该重点关注,右下角应该直接放弃。

mock

普通的单元测试在遇到依赖数据库链接或者Rpc接口的时候就会出现问题。这时候需要使用mock/stub测试: mock的作用:模拟链接的结果 需要用到的库:

一个例子说明patch的作用

func ReadFromRedis(key string) (string, error){
    return "kakak", nil
}

// 从redis里面取值,内部可能还包含其他业务逻辑
func GetValFromRedis(key string) string {
    val, err := ReadFromRedis(key)
    if err != nil {
        return "xx"
    }
    fmt.Println("val=", val)
    return val
}
//test.go
import (
    "fmt"
    "github.com/agiledragon/gomonkey"
    "github.com/smartystreets/goconvey/convey"
    "testing"
)
func TestGetValFromRedis(t *testing.T) {
    convey.Convey("one for success", t, func() {
        patches := gomonkey.ApplyFunc(ReadFromRedis, func(key string)(string, error) {
            return "123", nil
        })
        val, _ := ReadFromRedis("gggg")
        fmt.Println("valxxx=", val)
        defer patches.Reset()
        got := GetValFromRedis("1212121")
        convey.So(got, convey.ShouldEqual, "123")
    })
}

在上述例子中,需要测试的函数为GetValFromRedis,但是因为ReadFromRedis函数是需要访问redis的(领会意思,这里就没有实际访问了),所以正常的测试可能会出现连接问题(比如测试网络肯定是连不上的) 所以,使用gomonkey.ApplyFunc,就可以直接将ReadFromRedis在该函数的作用域下替换掉,这样GetValFromRedis所调用的函数将会是我们新定义的函数。 不过打桩可能会因为函数内联而失败。内联就是说函数会直接在调用处展开。闭包调用、select、for、defer、go创建的协程不会内联,其他的都有可能内联,比如这个。对此,需要在运行单元测试的时候加上-gcflags=all=-l

总结:对于一些无法完成本地调用的函数,我们只关心其返回结果,因此可以打桩。