『No11:Go 单元测试』

12.png
golang-11.png

大家好,我叫谢伟,是一名程序员。

最近更新不是很频繁,主要是我手头有好些事需要解决,比如更换环境,比如出去见识人,以便更好的认识自己,知道自己的短板在哪。

很早之前我就意识到:每隔半年需要出去走走,哪怕不是真的更换工作,你也应该出去走走,去市场检验一下自己是否在对应的岗位有竞争力,你的市场价位是多少。

好,本节的主题是:单元测试。

测试其实分很多种,就我在企业中的认识,一般称为测试工程师,从事的应该是所谓的集成测试(或者说是 AC测试(Acceptance Criteria,验收准则))。这一类的测试是从用户层面进行的测试。

比如你需要测试一个 PaaS 安装部署的功能。集成测试会怎么做?即完全按照用户的角度进行操作,比如部署之前的参数设置,参数设置完进行执行命令,部署完成查询一些参数等。

那AC 测试如何完全枚举这些用户行为呢?有各种各样的框架,比如MFQ ,这套框架本质是对金字塔原理的诠释,即:完全穷尽、相互独立。

还有一类测试称为FT(Functional Test) 即功能测试。我讲其中的一种吧。比如微服务领域,大多数服务其实是RESTful API 的形式。如何进行功能测试?大多数使用的是契约测试,即也是使用框架,对生产者和消费者独立测试,消费者和生产者相互独立,相互解耦,调用API,和预期的结果对比。

今天我们的主题是:单元测试(UT)

即完成的是对函数级的测试,测试是保证代码质量重要的一环。大厂一般合入代码都有一套流水线,什么意思呢。即你提交代码,自动会触发UT, 运行程序内的单元测试,单元测试之后有一定的质量统计,比如覆盖率,一般的大厂的代码覆盖了阈值是90%, 即提交代码,UT 运行之后,代码的覆盖率达到 90% 才可以合入。否则,先完成代码覆盖率。

编程领域内还有一个重要的思想,叫TDD, 即测试驱动开发。

编写一个测试,再写函数,直到测试通过,如此循坏。(当然实际上测试驱动开发,真正实施还是略微有点困难,一般的做法都是开发、测试,而不是测试、开发、测试、开发)

一般的初学者,是不太会关注测试,在没有进入职场之前,我甚至完全没关注测试,直到走入职场...


1. 编写函数

这里我们列举一个非常简单的例子,实现两数相加。

func Add(argOne int, argTwo int) int {
    return argOne + argTwo
}

没问题吧。两数相加。

2. 编写测试

测试需要有下面这些规范:

  • 文件名:_test.go 结尾
  • 函数名:Test 开头
  • 入参:(t *testing.T)
  • 内置库:testing
  • 报错信息:使用 testing 内置的方法:Errorf、Error 、Fail、Failed、Fatal、Fatalf、Log、Logf 等

一般的测试这么写

func TestAdd2(t *testing.T) {
    var result int
    result = Add(1, 2)
    if result != 3 {
        t.Errorf("wrong: result=%d actual=%d", result, 3)
    }
}

我这边只是举了个特别简单的例子,1+2=3, 实际上一般的测试例子应该选一些有代表性的,比如是否会越界啊、入参是否正确啊、等等。

上面的例子存在什么问题呢?

  • 测试数据和函数紧密耦合
  • 不利于写多个测试

如何解决这个问题呢?

  • 表格测试法:测试数据 和 函数 低耦合,便于写出多个测试用例
func TestAdd(test *testing.T) {
    tt := []struct {
        argOne int
        argTwo int
        result int
    }{
        {
            argOne: 1,
            argTwo: 2,
            result: 3,
        },
        {
            argOne: -1,
            argTwo: 1,
            result: 0,
        },
        {
            argOne: math.MaxInt8,
            argTwo: 1,
            result: 1 << 7,
        }, {
            argOne: math.MaxInt16,
            argTwo: 1,
            result: 1 << 15,
        },
    }
    for _, t := range tt {
        var result int
        result = Add(t.argOne, t.argTwo)
        if result != t.result {
            test.Errorf("wrong: result=%d actual=%d", result, t.result)
        }
    }
}

先给定一堆测试数据,再遍历测试数据,调用函数,看结果是否和预期一致。遍历过程中不知道预期值,不重要,调用下函数即可,根据报错信息,再进行修正。比如 math.MaxInt8 + 1 我可能不知道等于多少。那么可以 result = 0, 再看报错信息,纠正 result 即可。

这样测试数据和函数隔离,能写出更好的测试用例。

当然真实的情况远比这个例子需要复杂,比如:遇到了网络连接、遇到了读写文件、遇到了操作数据库。

这些一般怎么处理呢?测试中有一个名词叫 mock , 即打桩,意思是,给某个地方模拟它的值,即给定一个假的符合要求的值,比如网络请求,需要得到网页信息,那真实的单元测试不进行真实的网络操作,可以将请求打桩,返回一个指定的网页信息即可。

打桩又分给过程打桩,给函数打桩,给变量打桩等。这些问题,下次再补充,今天只讲单元测试。

3. 测试框架

内置的 testing 库其实挺好用的,但遇到复杂的问题,还是需要即用一些成熟的第三方库的测试框架。

GoConvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性。

func TestAdd3(t *testing.T) {
    Convey("Testing Add", t, func() {
        tt := []struct {
            a int
            b int
            c int
        }{
            {
                a: 1,
                b: 2,
                c: 3,
            },
            {
                a: 4,
                b: 5,
                c: 9,
            },
        }
        So(Add(tt[0].a, tt[0].b), ShouldEqual, tt[0].c)
        So(Add(tt[1].a, tt[1].b), ShouldEqual, tt[1].c)
    })
}

还支持嵌套,文档:GoConvey

4. 如何运行测试用例

如果你使用的是Goland , 那么你可以单个测试进行运行。

go-test.png

也可以终端下运行:(测试文件所在目录,比如 add_test.go 所在目录)

go test 

结果:

..
2 total assertions

PASS
ok      go-example-for-live/eleven/infra        0.070s

想查看更详细的信息:

go test -v
λ go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestAdd2
--- PASS: TestAdd2 (0.00s)
=== RUN   TestAdd3

  Testing Add ..


2 total assertions

--- PASS: TestAdd3 (0.00s)
PASS
ok      go-example-for-live/eleven/infra        0.067s

go test -run=Add -v

支持 正则,即所有以Add 开头的测试函数都会被运行。

λ go test -run=Add -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   TestAdd2
--- PASS: TestAdd2 (0.00s)
=== RUN   TestAdd3

  Testing Add ..


2 total assertions

--- PASS: TestAdd3 (0.00s)
PASS
ok      go-example-for-live/eleven/infra        0.076s

5. 覆盖率相关

上文讲过,一般的大厂,代码的合入有一定的准则,覆盖率是其中的一项,那如何使用 go 自带的命令行工具进行覆盖率的操作呢?

λ go test  -coverprofile cover.out
..
2 total assertions

PASS
coverage: 100.0% of statements
ok      go-example-for-live/eleven/infra        0.059s

当前目录下一个 cover.out 文件, 上文显示 Add 函数的覆盖率为 100%。

当然这只是一个文件的操作,那如何进行整个项目所有测试用例的测试是否通过呢?

官方没给出答案,所以可以借助第三方,或者自己写,本质上进行代码行数的统计,和测试用例覆盖率的统计,再进行汇总,得出整个项目的覆盖率的统计,这样虽然有可能不太准确,但至少是一种思路。

6. 总结

本节探讨了go 中的单元测试的编写,主要是包括:一般单元测试的编写、表格驱动的单元测试的编写、第三方库框架的单元测试的编写。

希望对你有所启发。

如果你对TDD 感兴趣,可以看看 Github 上这个项目:learn-go-with-tests

再会,我是谢伟。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容