用GoConvey编写单元测试的一些总结

一、尽量用Convey将所有测试用例的Convey汇总

用Convey嵌套的方法,将所有测试用例的Convey用一个大的Convey包裹起来,每个测试函数下只有一个大的Convey。比如下面的示例代码:

Go 复制代码
import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqual", t, func() {
        Convey("should return true when a != nil  && b != nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeTrue)
        })

        Convey("should return true when a == nil  && b == nil", func() {
            So(StringSliceEqual(nil, nil), ShouldBeTrue)
        })

        Convey("should return false when a == nil  && b != nil", func() {
            a := []string(nil)
            b := []string{}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })

        Convey("should return false when a != nil  && b != nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            So(StringSliceEqual(a, b), ShouldBeFalse)
        })
    })
}

这样做的好处是,看单测结果更为清晰直观:

Go 复制代码
=== RUN   TestStringSliceEqual

  TestStringSliceEqual 
    should return true when a != nil  && b != nil ✔
    should return true when a == nil  && b == nil ✔
    should return false when a == nil  && b != nil ✔
    should return false when a != nil  && b != nil ✔


4 total assertions

--- PASS: TestStringSliceEqual (0.00s)
PASS
ok      infra/alg       0.006s

二、用GWT结构来描述复杂的测试用例

GWT结构嵌套了三层Convey:最外层是Given层,用来给定测试用例需要的数据;中间一层是When层,用来执行被测试的函数方法,得到result;最内层是Then层,用So来对result进行断言,看结果是否满足期望。

1 示例代码

示例代码如下:

Go 复制代码
func TestStringSliceEqualIfBothNil(t *testing.T) {
    Convey("Given two string slice which are both nil", t, func() {
        var a []string = nil
        var b []string = nil
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be true", func() {
                So(result, ShouldBeTrue)
            })
        })
    })
}

func TestStringSliceNotEqualIfNotBothNil(t *testing.T) {
    Convey("Given two string slice which are both nil", t, func() {
        a := []string(nil)
        b := []string{}
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be false", func() {
                So(result, ShouldBeFalse)
            })
        })
    })
}

func TestStringSliceNotEqualIfBothNotNil(t *testing.T) {
    Convey("Given two string slice which are both not nil", t, func() {
        a := []string{"hello", "world"}
        b := []string{"hello", "goconvey"}
        Convey("When the comparision is done", func() {
            result := StringSliceEqual(a, b)
            Convey("Then the result should be false", func() {
                So(result, ShouldBeFalse)
            })
        })
    })
}

在实际运用中,可以结合第一条方法构成四层嵌套来描述一个测试用例:

Go 复制代码
func TestStringSliceEqual(t *testing.T) {
    Convey("TestStringSliceEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "goconvey"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceEqualIfBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            var a []string = nil
            var b []string = nil
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be true", func() {
                    So(result, ShouldBeTrue)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfNotBothNil", t, func() {
        Convey("Given two string slice which are both nil", func() {
            a := []string(nil)
            b := []string{}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

    Convey("TestStringSliceNotEqualIfBothNotNil", t, func() {
        Convey("Given two string slice which are both not nil", func() {
            a := []string{"hello", "world"}
            b := []string{"hello", "goconvey"}
            Convey("When the comparision is done", func() {
                result := StringSliceEqual(a, b)
                Convey("Then the result should be false", func() {
                    So(result, ShouldBeFalse)
                })
            })
        })
    })

}

2 大坑

注意!Given层中最好只有一个Then,因为多个Then会导致每执行完一个Then就会再次执行一遍被测试的函数方法,导致多次执行的结果可能并不相同从而导致意料之外的错误(比如上面示例中的"result := StringSliceEqual(a, b)")。所以如果选择使用GWT的结构,那么就要保证W中只有一个T,最好也要保证G中只有一个W。

三、自定义断言函数

断言函数So中第二个参数Assertion类型定义:

Go 复制代码
type Assertion func(actual interface{}, expected ...interface{}) string

返回空字符串表示断言成功,否则就是断言失败了。

1 自定义断言函数

所以我们自定义断言函数时也要注意这点,下面是一个参考示例:

Go 复制代码
func ShouldSummerBeComming(actual interface{}, expected ...interface{}) string {
    if actual == "summer" && expected[0] == "comming" {
        return ""
    } else {
        return "summer is not comming!"
    }
}

上述代码中,第一个条件表示断言成功,其它所有情况都是断言失败。

2 在So中使用自定义的断言函数

Go 复制代码
func TestSummer(t *testing.T) {
    Convey("TestSummer", t, func() {
        So("summer", ShouldSummerBeComming, "comming")
        So("winter", ShouldSummerBeComming, "comming")
    })
}

测试结果:

Go 复制代码
=== RUN   TestSummer

  TestSummer ✔✘


Failures:

  * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go 
  Line 52:
  summer is not comming!


2 total assertions

--- FAIL: TestSummer (0.00s)
FAIL
exit status 1
FAIL    infra/alg       0.006s
相关推荐
FellAveal7 小时前
【Go语言入门学习笔记】Part19.速率控制、原子计数器、互斥锁(Mutex)、状态协程
笔记·学习·golang
Python私教8 小时前
Codex 写出的代码能跑却算错钱:我用 3 个测试拆穿一次 AI 编程幻觉
python·单元测试·ai编程
极地野狼 音乐哔哔8 小时前
AI Agent 30天速成|Day4 笔记
人工智能·笔记
oier_Asad.Chen9 小时前
【洛谷题解/AcWing题解】洛谷P4011 孤岛营救问题/AcWing1131拯救大兵瑞恩
数据结构·笔记·学习·算法·动态规划·图论·宽度优先
xxwl5859 小时前
Git 完整学习笔记:从入门到团队协作
笔记·git·学习
罗西的思考10 小时前
【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 — (14)— Teacher
人工智能·笔记·深度学习·算法·机器学习
你想知道什么?12 小时前
决策树-学习笔记
笔记·学习·决策树
hongmai66688812 小时前
ESP32-C5-WROOM-1-N16R8:双频Wi-Fi 6与多协议融合,重新定义物联网连接新标准
笔记·嵌入式硬件·物联网·智能路由器·risc-v
风曦Kisaki12 小时前
Kubernetes(K8s)笔记Day04:控制器(ReplicaSet 与Deployment),滚动更新及回滚,滚动更新策略,Pod 的 DNS 策略
linux·运维·笔记·docker·容器·kubernetes
星恒随风12 小时前
C++ STL 详解:set 与 multiset 的使用、区间查询和算法应用
开发语言·c++·笔记·学习·算法