Go 语言基础之单元测试

本文介绍 Go 语言的单元测试。

单元测试基础

Go 语言依赖go test命令进行函数测试,包目录下所有以_test.go为后缀的文件都是go test测试的一部分,不会被go build编译到最终的可执行文件。在*_test.go文件中,可以编写以下三种类型的函数:

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档

上述函数中,最常使用的就是测试函数。

testing.T

☕️ 常用方法

// 单元测试函数必须导入 testing 包,函数前缀名必须以 Test 开头,后缀名必须以大写字母开头
func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }
// 参数 t 是用于报告测试失败和附加的日志信息。testing.T 的拥有的方法如下:
// Log 使用与 Println 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面
// 1) 对于测试来说,格式化文本只会在测试失败或者设置了 test -v 标志的情况下被打印出来
// 2) 对于基准测试来说,为了避免 test -v 标志的值对测试的性能产生影响, 格式化文本总会被打印出来
func (c *T) Log(args ...interface{})

// Log 使用与 Printf 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面
// 如果输入的格式化文本最末尾没有出现新行,那么将一个新行添加到格式化后的文本末尾
// 1) 对于测试来说,Logf 产生的格式化文本只会在测试失败或者设置了 test -v 标志的情况下被打印出来
// 2) 对于基准测试来说,为了避免 test -v 标志的值对测试的性能产生影响,Logf 产生的格式化文本总会被打印出来
func (c *T) Logf(format string, args ...interface{})

// 将当前测试标识为失败,但是仍继续执行该测试
func (c *T) Fail()

// 将当前测试标识为失败并停止执行该测试,在此之后,测试过程将在下一个测试或者下一个基准测试中继续
// FailNow 必须在运行测试函数或者基准测试函数的 goroutine 中调用,而不能在测试期间创建的 goroutine 中调用。调用 FailNow 不会导致其他 goroutine 停止
func (c *T) FailNow()

// Failed 用于报告测试函数是否已失败
func (c *T) Failed() bool

// 调用 Error 相当于在调用 Log 之后调用 Fail 
func (c *T) Error(args ...interface{})

// 调用 Errorf 相当于在调用 Logf 之后调用 Fail 
func (c *T) Errorf(format string, args ...interface{})

// 调用 Fatal 相当于在调用 Log 之后调用 FailNow
func (c *T) Fatal(args ...interface{})

// 调用 Fatalf 相当于在调用 Logf 之后调用 FailNow
func (c *T) Fatalf(format string, args ...interface{})

// 将当前测试标识为“被跳过”并停止执行该测试。 如果一个测试在失败(参考 Error、Errorf 和 Fail)之后被跳过了, 那么它还是会被判断为是“失败的”
// 在停止当前测试之后,测试过程将在下一个测试或者下一个基准测试中继续,具体请参考 FailNow 
// SkipNow 必须在运行测试的 goroutine 中进行调用,而不能在测试期间创建的 goroutine 中调用。 调用 SkipNow 不会导致其他 goroutine 停止
func (c *T) SkipNow()

// 调用 Skip 相当于在调用 Log 之后调用 SkipNow 
func (c *T) Skip(args ...interface{})

// 调用 Skipf 相当于在调用 Logf 之后调用 SkipNow
func (c *T) Skipf(format string, args ...interface{})

// Skipped 用于报告测试函数是否已被跳过
func (c *T) Skipped() bool

// 返回正在运行的测试或基准测试的名字
func (c *T) Name() string

// Parallel 用于表示当前测试只会与其他带有 Parallel 方法的测试并行进行测试
func (t *T) Parallel()

// 执行名字为 name 的子测试 f ,并报告 f 在执行过程中是否出现了任何失败。Run 将一直阻塞直到 f 的所有并行测试执行完毕
// Run 可以在多个 goroutine 里面同时进行调用,但这些调用必须发生在 t 的外层测试函数(outer test function)返回之前
func (t *T) Run(name string, f func(t *T)) bool

⭐️ 示例代码

// demo/split.go
package demo

import "strings"

// Split 把字符串s按照给定的分隔符sep进行分割返回字符串切片
func Split(s, sep string) (result []string) {
    i := strings.Index(s, sep)

    for i > -1 {
        result = append(result, s[:i])
        s = s[i+len(sep):]
        i = strings.Index(s, sep)
    }
    result = append(result, s)
    return
}
// demo/split_test.go,测试文件命令必须以 _test.go 为后缀
package demo

import (
    "reflect"
    "testing"
)

func TestSplit(t *testing.T) { // 测试函数名必须以 Test 开头,必须接收一个 *testing.T 类型参数
    got := Split("a:b:c", ":")         // 程序输出的结果
    want := []string{"a", "b", "c"}    // 期望的结果
    if !reflect.DeepEqual(got, want) { // 因为 slice 不能比较直接,借助反射包中的方法比较
        t.Errorf("expected:%v, got:%v", want, got) // 测试失败输出错误提示
    }

}

func TestSplitWithComplexSep(t *testing.T) {
    got := Split("abcd", "bc")
    want := []string{"a", "d"}
    if !reflect.DeepEqual(got, want) {
        t.Errorf("expected:%v, got:%v", want, got)
    }
}

✏️ 运行结果

> go test
PASS
ok      test/demo       0.474s
// -v 参数,会输出完整的测试结果
> go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestSplitWithComplexSep
--- PASS: TestSplitWithComplexSep (0.00s)
PASS
ok      test/demo       0.452s
// -run 参数对应着一个正则表达式,只要函数名匹配的测试函数才会被执行
> go test -v -run=Sep
=== RUN   TestSplitWithComplexSep
--- PASS: TestSplitWithComplexSep (0.00s)
PASS
ok      test/demo       0.461s
// -cover 参数,会输出测试覆盖率
> go test -cover     
PASS
coverage: 100.0% of statements
ok      test/demo       0.479s

t.Skip() 方法

// 为了节省时间支持在单元测试时跳过某些耗时的测试用例
func TestTimeConsuming(t *testing.T) {
    if testing.Short() {
        t.Skip("short模式下会跳过该测试用例")
    }
    ...
}

当执行go test -short时就不会执行上面的TestTimeConsuming测试用例。


t.Run() 方法

// 支持在测试函数中使用 t.Run() 方法执行一组测试用例,无需为不同的测试数据定义多个测试函数
func TestXxx(t *testing.T){
    t.Run("case1", func(t *testing.T){...})
    t.Run("case2", func(t *testing.T){...})
    t.Run("case3", func(t *testing.T){...})
}

表格驱动测试

// demo/split_test.go,测试文件命令必须以 _test.go 为后缀
package demo

import (
    "reflect"
    "testing"
)

func TestSplitAll(t *testing.T) {
    // 定义测试表格
    // 这里使用匿名结构体定义了若干个测试用例,并为每个测试用例设置了一个名称
    tests := []struct {
        name  string
        input string
        sep   string
        want  []string
    }{
        {"base case", "a:b:c", ":", []string{"a", "b", "c"}},
        {"wrong sep", "a:b:c", ",", []string{"a:b:c"}},
        {"more sep", "abcd", "bc", []string{"a", "d"}},
        {"leading sep", "沙河有沙又有河", "沙", []string{"", "河有", "又有河"}},
    }
    // 遍历测试用例
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) { // 使用 t.Run() 执行子测试
            got := Split(tt.input, tt.sep)
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("expected:%#v, got:%#v", tt.want, got)
            }
        })
    }
}
go test -v        
=== RUN   TestSplitAll
=== RUN   TestSplitAll/base_case
=== RUN   TestSplitAll/wrong_sep
=== RUN   TestSplitAll/more_sep
=== RUN   TestSplitAll/leading_sep
--- PASS: TestSplitAll (0.00s)
    --- PASS: TestSplitAll/base_case (0.00s)
    --- PASS: TestSplitAll/wrong_sep (0.00s)
    --- PASS: TestSplitAll/more_sep (0.00s)
    --- PASS: TestSplitAll/leading_sep (0.00s)
PASS
ok      test/demo       1.011s

t.Parallel() 方法

// demo/split_test.go,测试文件命令必须以 _test.go 为后缀
package demo

import (
    "reflect"
    "testing"
)

func TestSplitAll(t *testing.T) {
    t.Parallel() // 将 TLog 标记为能够与其他测试并行运行
    // 定义测试表格
    // 这里使用匿名结构体定义了若干个测试用例
    // 并且为每个测试用例设置了一个名称
    tests := []struct {
        name  string
        input string
        sep   string
        want  []string
    }{
        {"base case", "a:b:c", ":", []string{"a", "b", "c"}},
        {"wrong sep", "a:b:c", ",", []string{"a:b:c"}},
        {"more sep", "abcd", "bc", []string{"a", "d"}},
        {"leading sep", "沙河有沙又有河", "沙", []string{"", "河有", "又有河"}},
    }
    // 遍历测试用例
    for _, tt := range tests {
        tt := tt                            // 注意这里重新声明tt变量(避免多个goroutine中使用了相同的变量)
        t.Run(tt.name, func(t *testing.T) { // 使用t.Run()执行子测试
            t.Parallel() // 将每个测试用例标记为能够彼此并行运行
            got := Split(tt.input, tt.sep)
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("expected:%#v, got:%#v", tt.want, got)
            }
        })
    }
}
> go test -v
=== RUN   TestSplitAll
=== PAUSE TestSplitAll
=== CONT  TestSplitAll
=== RUN   TestSplitAll/base_case
=== PAUSE TestSplitAll/base_case
=== RUN   TestSplitAll/wrong_sep
=== PAUSE TestSplitAll/wrong_sep
=== RUN   TestSplitAll/more_sep
=== PAUSE TestSplitAll/more_sep
=== RUN   TestSplitAll/leading_sep
=== PAUSE TestSplitAll/leading_sep
=== CONT  TestSplitAll/base_case
=== CONT  TestSplitAll/leading_sep
=== CONT  TestSplitAll/more_sep
=== CONT  TestSplitAll/wrong_sep
--- PASS: TestSplitAll (0.00s)
    --- PASS: TestSplitAll/base_case (0.00s)
    --- PASS: TestSplitAll/leading_sep (0.00s)
    --- PASS: TestSplitAll/more_sep (0.00s)
    --- PASS: TestSplitAll/wrong_sep (0.00s)
PASS
ok      test/demo       0.485s

goconvey 使用

goconvey是一个非常好用的 Go 测试框架,它直接与go test集成,提供了很多丰富的断言函数,能够在终端输出可读的彩色测试结果,并且还支持全自动的Web UI

go get -u github.com/smartystreets/goconvey

断言函数

// 一般相等类
So(thing1, ShouldEqual, thing2)
So(thing1, ShouldNotEqual, thing2)
So(thing1, ShouldResemble, thing2) // a deep equals for arrays, slices, maps, and structs
So(thing1, ShouldNotResemble, thing2)
So(thing1, ShouldPointTo, thing2)
So(thing1, ShouldNotPointTo, thing2)
So(thing1, ShouldBeNil)
So(thing1, ShouldNotBeNil)
So(thing1, ShouldBeTrue)
So(thing1, ShouldBeFalse)
So(thing1, ShouldBeZeroValue)
// 数字数量比较类
So(1, ShouldBeGreaterThan, 0)
So(1, ShouldBeGreaterThanOrEqualTo, 0)
So(1, ShouldBeLessThan, 2)
So(1, ShouldBeLessThanOrEqualTo, 2)
So(1.1, ShouldBeBetween, .8, 1.2)
So(1.1, ShouldNotBeBetween, 2, 3)
So(1.1, ShouldBeBetweenOrEqual, .9, 1.1)
So(1.1, ShouldNotBeBetweenOrEqual, 1000, 2000)
So(1.0, ShouldAlmostEqual, 0.99999999, .0001)   // tolerance is optional; default 0.0000000001
So(1.0, ShouldNotAlmostEqual, 0.9, .0001)
// 包含类
So([]int{2, 4, 6}, ShouldContain, 4)
So([]int{2, 4, 6}, ShouldNotContain, 5)
So(4, ShouldBeIn, ...[]int{2, 4, 6})
So(4, ShouldNotBeIn, ...[]int{1, 3, 5})
So([]int{}, ShouldBeEmpty)
So([]int{1}, ShouldNotBeEmpty)
So(map[string]string{"a": "b"}, ShouldContainKey, "a")
So(map[string]string{"a": "b"}, ShouldNotContainKey, "b")
So(map[string]string{"a": "b"}, ShouldNotBeEmpty)
So(map[string]string{}, ShouldBeEmpty)
So(map[string]string{"a": "b"}, ShouldHaveLength, 1) // supports map, slice, chan, and string
// 字符串类
So("asdf", ShouldStartWith, "as")
So("asdf", ShouldNotStartWith, "df")
So("asdf", ShouldEndWith, "df")
So("asdf", ShouldNotEndWith, "df")
So("asdf", ShouldContainSubstring, "sd") // optional 'expected occurences' arguments?
So("asdf", ShouldNotContainSubstring, "er")
So("adsf", ShouldBeBlank)
So("asdf", ShouldNotBeBlank)
// panic 类
So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "")     // or errors.New("something")
So(func(), ShouldNotPanicWith, "")  // or errors.New("something")
// 类型检查类
So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, "asdf")
// 时间和时间间隔类
So(time.Now(), ShouldHappenBefore, time.Now())
So(time.Now(), ShouldHappenOnOrBefore, time.Now())
So(time.Now(), ShouldHappenAfter, time.Now())
So(time.Now(), ShouldHappenOnOrAfter, time.Now())
So(time.Now(), ShouldHappenBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldNotHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenWithin, duration, time.Now())
So(time.Now(), ShouldNotHappenWithin, duration, time.Now())

示例代码

package demo

import (
    c "github.com/smartystreets/goconvey/convey"
    "testing"
)

func TestSplitAll(t *testing.T) {
    c.Convey("基础用例", t, func() {
        var (
            s      = "a:b:c"
            sep    = ":"
            expect = []string{"a", "b", "c"}
        )
        got := Split(s, sep)
        c.So(got, c.ShouldResemble, expect) // 断言
    })

    c.Convey("不包含分隔符用例", t, func() {
        var (
            s      = "a:b:c"
            sep    = "|"
            expect = []string{"a:b:c"}
        )
        got := Split(s, sep)
        c.So(got, c.ShouldResemble, expect) // 断言
    })

    c.Convey("分隔符在开头或结尾用例", t, func() {
        tt := []struct {
            name   string
            s      string
            sep    string
            expect []string
        }{
            {"分隔符在开头", "*1*2*3", "*", []string{"", "1", "2", "3"}},
            {"分隔符在结尾", "1+2+3+", "+", []string{"1", "2", "3", ""}},
        }
        for _, tc := range tt {
            c.Convey(tc.name, func() { // 嵌套调用 Convey,只需要在顶层的 Convey 调用时传入 t
                got := Split(tc.s, tc.sep)
                c.So(got, c.ShouldResemble, tc.expect)
            })
        }
    })
}

> go test -v
=== RUN   TestSplitAll

  基础用例 .

1 total assertion

  不包含分隔符用例 .

2 total assertions

  分隔符在开头或结尾用例
    分隔符在开头 .
    分隔符在结尾 .

4 total assertions

--- PASS: TestSplitAll (0.00s)
PASS
ok      test/demo       0.497s

gomonkey 使用

gomonkey是一个 Go 的打桩框架,提供了很多丰富的打桩函数/方法,旨在让用户在单元测试中低成本的完成打桩,将精力聚焦于业务功能的开发。

go get -u github.com/agiledragon/gomonkey

为函数打桩

// 为函数/接口打一个桩,target 表示函数名,double 表示桩函数
func ApplyFunc(target, double interface{}) *Patches
func (this *Patches) ApplyFunc(target, double interface{}) *Patches

// 为函数/接口打一个特定的桩序列,target 表示函数名,outputs 表示桩序列参数(返回值序列)
func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches
// Outputs 结构体中,Values 是一个可传递多个值切片,Times 指定返回的次数(值为 0 和 1 没有区别)
type Params []interface{}
type OutputCell struct {
    Values Params
    Times  int
}

☕️ 示例代码

// 对 fake.Exec() 函数进行 mock
func Exec(cmd string, args ...string) (string, error) {
    cmdPath, err := exec.LookPath(cmd)
    if err != nil {
        fmt.Errorf("exec.LookPath err: %v, cmd: %s", err, cmd)
        return "", errors.New("any")
    }

    var output []byte
    output, err = exec.Command(cmdPath, args...).CombinedOutput()
    if err != nil {
        fmt.Errorf("exec.Command.CombinedOutput err: %v, cmd: %s", err, cmd)
        return "", errors.New("any")
    }
    fmt.Println("CMD[", cmdPath, "]ARGS[", args, "]OUT[", string(output), "]")
    return string(output), nil
}
package demo

import (
    "github.com/agiledragon/gomonkey"
    "github.com/agiledragon/gomonkey/test/fake"
    c "github.com/smartystreets/goconvey/convey"
    "testing"
)

func TestAll(t *testing.T) {
    // 为函数/接口打一个桩
    c.Convey("TestApplyFunc", t, func() {
        patches := gomonkey.ApplyFunc(fake.Exec, func(cmd string, args ...string) (string, error) {
            return "hello world", nil
        })
        defer patches.Reset()  // 删除桩
        output, err := fake.Exec("", "")
        c.So(err, c.ShouldBeNil)
        c.So(output, c.ShouldEqual, "hello world")
    })

    // 为函数/接口打一个特定的桩序列
    c.Convey("TestApplyFuncSeq", t, func() {
        patches := gomonkey.ApplyFuncSeq(fake.Exec, []gomonkey.OutputCell{
            {Values: gomonkey.Params{"hello1", fake.ErrActual}, Times: 2}, // 指定第一次和第二次返回的序列
            {Values: gomonkey.Params{"hello2", nil}},                      // 指定第三次返回的序列
        })
        defer patches.Reset()  // 删除桩
        output1, err := fake.Exec("", "")
        c.So(err, c.ShouldResemble, fake.ErrActual)
        c.So(output1, c.ShouldEqual, "hello1")

        output2, err := fake.Exec("", "")
        c.So(err, c.ShouldResemble, fake.ErrActual)
        c.So(output2, c.ShouldEqual, "hello1")

        output3, err := fake.Exec("", "")
        c.So(err, c.ShouldBeNil)
        c.So(output3, c.ShouldEqual, "hello2")
    })
}

为成员方法打桩

// 为成员方法打一个桩,target 表示对象类型,methodName 表示对象的方法名,double 表示桩函数
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches

// 为成员方法打一个特定的桩序列,target 表示对象类型,methodName 表示对象的方法名,outputs 表示桩序列参数(返回值序列)
func ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches
func (this *Patches) ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches

☕️ 示例代码

// 对 fake.Slice 类型的 Add() 方法进行 mock
type Slice []int

func NewSlice() Slice {
    return make(Slice, 0)
}

func (this* Slice) Add(elem int) error {
    for _, v := range *this {
        if v == elem {
            fmt.Printf("Slice: Add elem: %v already exist\n", elem)
            return ErrElemExsit
        }
    }
    *this = append(*this, elem)
    fmt.Printf("Slice: Add elem: %v succ\n", elem)
    return nil
}

func (this* Slice) Remove(elem int) error {
    found := false
    for i, v := range *this {
        if v == elem {
            if i == len(*this) - 1 {
                *this = (*this)[:i]

            } else {
                *this = append((*this)[:i], (*this)[i+1:]...)
            }
            found = true
            break
        }
    }
    if !found {
        fmt.Printf("Slice: Remove elem: %v not exist\n", elem)
        return ErrElemNotExsit
    }
    fmt.Printf("Slice: Remove elem: %v succ\n", elem)
    return nil
}
package demo

import (
    "github.com/agiledragon/gomonkey"
    "github.com/agiledragon/gomonkey/test/fake"
    c "github.com/smartystreets/goconvey/convey"
    "reflect"
    "testing"
)

func TestAll(t *testing.T) {
    // 为成员方法打一个桩
    c.Convey("TestApplyMethod", t, func() {
        patches := gomonkey.ApplyMethod(reflect.TypeOf(new(fake.Slice)), "Add", func(this *fake.Slice, elem int) error {
            return nil
        })
        defer patches.Reset() // 删除桩

        slice := fake.NewSlice()

        err := slice.Add(1)
        c.So(err, c.ShouldBeNil)

        err = slice.Remove(1)
        c.So(err, c.ShouldResemble, fake.ErrElemNotExsit)
    })

    // 为成员方法打一个特定的桩序列
    c.Convey("TestApplyMenthodSeq", t, func() {
        patches := gomonkey.ApplyMethodSeq(reflect.TypeOf(new(fake.Slice)), "Add", []gomonkey.OutputCell{
            {Values: gomonkey.Params{nil}},                      // 指定第一次返回的序列
            {Values: gomonkey.Params{fake.ErrActual}, Times: 2}, // 指定第二次和第三次返回的序列
        })
        defer patches.Reset() // 删除桩

        slice := fake.NewSlice()

        err := slice.Add(1)
        c.So(err, c.ShouldBeNil)

        err = slice.Add(1)
        c.So(err, c.ShouldResemble, fake.ErrActual)

        err = slice.Add(1)
        c.So(err, c.ShouldResemble, fake.ErrActual)
    })
}

为函数变量打桩

// 为函数变量打一个桩,target 表示函数变量,double 表示桩函数
func ApplyFuncVar(target, double interface{}) *Patches
func (this *Patches) ApplyFuncVar(target, double interface{}) *Patches

// 为函数变量打一个特定的桩序列,target 表示函数变量,outputs 表示桩序列参数(返回值序列)
func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches
func (this *Patches) ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches

☕️ 示例代码

// 对 fake.Marshal 进行 mock
var Marshal = func(v interface{}) ([]byte, error) {
    return nil, nil
}
package demo

import (
    "github.com/agiledragon/gomonkey"
    "github.com/agiledragon/gomonkey/test/fake"
    c "github.com/smartystreets/goconvey/convey"
    "testing"
)

func TestAll(t *testing.T) {
    // 为函数变量打一个桩
    c.Convey("TestApplyFuncVal", t, func() {
        patches := gomonkey.ApplyFuncVar(&fake.Marshal, func(v interface{}) ([]byte, error) {
            return []byte("hello world"), nil
        })
        defer patches.Reset() // 删除桩

        bytes, err := fake.Marshal(nil)
        c.So(err, c.ShouldBeNil)
        c.So(string(bytes), c.ShouldEqual, "hello world")
    })

    // 为函数变量打一个特定的桩序列
    c.Convey("TestApplyFuncValSeq", t, func() {
        patches := gomonkey.ApplyFuncVarSeq(&fake.Marshal, []gomonkey.OutputCell{
            {Values: gomonkey.Params{[]byte("hello1"), nil}},           // 指定第一次返回的序列
            {Values: gomonkey.Params{[]byte("hello2"), nil}, Times: 2}, // 指定第二次和第三次返回的序列
        })
        defer patches.Reset() // 删除桩

        bytes, _ := fake.Marshal(nil)
        c.So(string(bytes), c.ShouldEqual, "hello1")

        bytes, _ = fake.Marshal(nil)
        c.So(string(bytes), c.ShouldEqual, "hello2")

        bytes, _ = fake.Marshal(nil)
        c.So(string(bytes), c.ShouldEqual, "hello2")
    })
}

为全局变量打桩

// 为全局变量打一个桩,target 表示全局变量,double 表示桩函数
func ApplyGlobalVar(target, double interface{}) *Patches
func (this *Patches) ApplyGlobalVar(target, double interface{}) *Patches

☕️ 示例代码

package demo

import (
    "github.com/agiledragon/gomonkey"
    c "github.com/smartystreets/goconvey/convey"
    "testing"
)

var num = 10

func TestAll(t *testing.T) {
    // 为全局变量打一个桩
    c.Convey("TestApplyFuncVal", t, func() {
        patches := gomonkey.ApplyGlobalVar(&num, 100)
        defer patches.Reset() // 删除桩

        c.So(num, c.ShouldEqual, 100)
    })
}


参考

  1. Go单测从零到溜系列0—单元测试基础
  2. Go单测从零到溜系列5—goconvey的使用
  3. gomonkey总结
posted @ 2022-06-11 15:14  呵呵233  阅读(484)  评论(0编辑  收藏  举报