前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >测试驱动开发与 golang 单元测试

测试驱动开发与 golang 单元测试

作者头像
用户3147702
发布2022-06-27 14:15:27
9430
发布2022-06-27 14:15:27
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

在现代程序设计中,测试显得越来越重要,未经测试就在线上供用户使用其后果很可能是灾难性的。

2. 测试驱动开发

软件开发界泰斗 Kent Beck 先生甚至在《Test Driven Development: By Example》一书中提出了著名的测试驱动开发理论 — TDD。

众所周知,在盖房子前,先拉起基准线,再比照着线来砌砖是一个好习惯,而在软件开发中,TDD 就是这个基准线,他要求在开发工作开始前,先根据用户需求编写测试用例,再在开发的过程中不断用测试用例校验代码,直到完全通过即意味着开发完成。 同时,历史的所有测试用例都持续保留,可以保证新增需求对老功能影响的可控性。

2.1. 优点

  1. 提升工程质量 — 丰富的测试用例让开发者的开发更加专注,能够做到有的放矢,从而减轻压力与程序设计过程中的不可控因素
  2. 提升开发效率 — 敏捷开发变得可行
  3. 更容易重构 — 完整的测试用例十分便于回归测试,在重构过程中,丰富的回归测试让重构过程更加可控

2.2. 缺点

  1. 可能造成开发人员将注意力过度集中于单元测试用例,而忽略更加长期的规划
  2. 开发过程需要额外维护所有单元测试用例与回归测试用例的正确性,增大开发成本,尤其是在实际工程开发中,需求总是会发生变化,这会造成测试用例的频繁更改,更加令人难以维护
  3. GUI、web 页面等难以编写测试用例

3. golang 测试工具

在很多企业中都或多或少的应用着 TDD 的思想,而其本质上是企业对于软件测试的重视,在开发过程中,不断的测试,让问题尽早的暴露和扼杀,避免问题的扩散,降低不可控性。 现代编程语言中,很多都集成了测试工具,例如 golang 中,就有 testing 包提供一系列测试工具。 通过 go test 命令就可以实现测试用例的执行,通过不同的参数还可以进行例如压测、并发测试等测试功能。 下面就来详细介绍一下。

4. 单元测试

单元测试是最为常见和常用的测试方法。 只要在项目文件中写入下面的方法:

代码语言:javascript
复制
func TestXxx(*testing.T) {
// 测试函数体
}

然后执行:

go test .

就可以看到编译、运行后的测试结果了。

4.1. 示例

4.1.1. 测试通过

我们编写一个斐波那契数列运算的函数:

代码语言:javascript
复制
func Fib(n int) int {
        if n < 2 {
                return n
        }
        return Fib(n-1) + Fib(n-2)
}

编写单元测试代码:

代码语言:javascript
复制
func TestFib(t *testing.T) {
    var (
        in       = 7
        expected = 13
    )
    actual := Fib(in)
    if actual != expected {
        t.Errorf("Fib(%d) = %d; expected %d", in, actual, expected)
    }
}

执行 go test . 输出了:

ok chapter09/testing 0.007s

测试通过。

4.1.2. 测试失败

我们稍稍修改一下代码:

代码语言:javascript
复制
func Fib(n int) int {
        if n < 2 {
                return n
        }
        return Fib(n-1) + Fib(n-1)
}

执行 go test . 可以看到:

—- FAIL: TestSum (0.00s) t_test.go:16: Fib(10) = 64; expected 13 FAIL FAIL chapter09/testing 0.009s

显然,测试失败了。

5. testing.T 中的报告方法

上面的例子中,我们使用到了 testing.T 中的 Errorf 方法,他打印出了错误信息,但事实上,他并不会中断程序的执行。 而 testing.T 类提供了几个十分常用的报告方法。

5.1. testing.T

testing.T 的结构定义如下:

代码语言:javascript
复制
type T struct {
    common
    isParallel bool
    context    *testContext // For running tests and subtests.
}

common 是一个 struct,他为 T 类型提供了所有的报告方法。

5.2. common 提供的报告方法

testing.T 的报告方法

方法名

声明

说明

Log

Log(args …interface{})

输出信息

Logf

Logf(format string, args …interface{})

格式化输出信息

Fail

Fail()

提示用户测试失败并继续

FailNow

FailNow()

提示用户测试失败并中止测试(通过调用 runtime.Goexit())

Error

Error(args …interface{})

提示用户测试错误并打印信息,通过调用 Log + Fail 实现

Errorf

Errorf(format string, args …interface{})

Error 方法的格式化输出版本

SkipNow

SkipNow()

跳出测试(通过调用 runtime.Goexit())

Skip

Skip(args …interface{})

打印信息并退出测试,通过调用 Log 与 SkipNow 实现

Skipf

Skipf(format string, args …interface{})

Skip 的格式化输出版本

Fatal

Fatal(args …interface{})

输出日志、提示用户测试失败并退出,通过调用 Log 与 FailNow 实现

Fatalf

Fatalf(format string, args …interface{})

Fatal 的格式化输出版本

6. 子测试

掌握了上面的内容,你就可以为你的代码编写合适的测试用例了。 但是,有的时候你想要像函数调用一样嵌套多个单元测试,或者想在若干个测试开始前或结束后做一些事情,这在 go 语言中有着很好的支持。 golang 1.7 版本开始,引入了一个新特性 — 子测试。

6.1. 示例

代码语言:javascript
复制
func TestFoo(t *testing.T) {
    // <setup code>
    t.Run("A=1", func(t *testing.T) { ... })
    t.Run("A=2", func(t *testing.T) { ... })
    t.Run("B=1", func(t *testing.T) { ... })
    // <tear-down code>
}

6.2. 执行

go test -run ’’ # Run 所有测试。 go test -run Foo # Run 匹配 "Foo" 的顶层测试,例如 "TestFoo"、"TestFooBar"。 go test -run Foo/A= # 匹配顶层测试 "Foo",运行其匹配 "A=" 的子测试。 go test -run /A=1 # 运行所有匹配 "A=1" 的子测试。

6.3. 子测试并发执行 — t.Parallel()

很多情况下,我们并不想等着若干个子测试一个个顺次执行,而是希望能够让他们相互并发执行,这时 t.Parallel() 就派上用场了。 当然,t.Parallel() 并不仅仅能够应用在子测试中,任何几个测试函数中,只要调用了 t.Parallel(),他们之间都会并发执行。

代码语言:javascript
复制
func TestGroupedParallel(t *testing.T) {
    for _, tc := range tests {
        tc := tc // capture range variable
        t.Run(tc.Name, func(t *testing.T) {
            t.Parallel()
            ...
        })
    }
}

7. TestMain

子测试让我们能够嵌套测试函数,在若干个测试函数之前、之后或之间进行一些操作。 但我们是否可以定义,无论在什么情况下,只要测试函数执行,他前后就必须执行一些操作呢? golang 用 TestMain 可以实现这样的特性。

func TestMain(m *testing.M)

只要测试文件中包含该函数,那么,无论执行测试文件中的哪个函数,都会先去运行 TestMain 函数。 在 TestMain 函数中,通过 m.Run() 就可以调用本次预期将会执行的测试函数。 不难看出,这是一个面向切面编程思想的应用。

7.1. 示例

代码语言:javascript
复制
func TestMain(m *testing.M) {
    // do someting setup
    exitCode := m.Run()
    os.Exit(exitCode)
    // do something teardow
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 测试驱动开发
    • 2.1. 优点
      • 2.2. 缺点
      • 3. golang 测试工具
      • 4. 单元测试
        • 4.1. 示例
          • 4.1.1. 测试通过
          • 4.1.2. 测试失败
      • 5. testing.T 中的报告方法
        • 5.1. testing.T
          • 5.2. common 提供的报告方法
          • 6. 子测试
            • 6.1. 示例
              • 6.2. 执行
                • 6.3. 子测试并发执行 — t.Parallel()
                • 7. TestMain
                  • 7.1. 示例
                  相关产品与服务
                  项目管理
                  CODING 项目管理(CODING Project Management,CODING-PM)工具包含迭代管理、需求管理、任务管理、缺陷管理、文件/wiki 等功能,适用于研发团队进行项目管理或敏捷开发实践。结合敏捷研发理念,帮助您对产品进行迭代规划,让每个迭代中的需求、任务、缺陷无障碍沟通流转, 让项目开发过程风险可控,达到可持续性快速迭代。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档