前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >使用 Go Convey 做BDD测试的入门指南

使用 Go Convey 做BDD测试的入门指南

作者头像
KevinYan
发布2025-02-25 09:25:45
发布2025-02-25 09:25:45
6700
代码可运行
举报
文章被收录于专栏:网管叨bi叨网管叨bi叨
运行总次数:0
代码可运行

前面在「Go 代码测试时怎么打桩?给大家写了几个常用案例」中我们介绍了在单元测试中使用gomonkey为代码进行打桩的各种方法。

今天我们介绍在Go单元测试中另外一个很好用的工具库goconvey,上面说的gomonkey属于在 Test Double 方面提供能力,也就是我们通常说的mock,用它们可以自定义一套实现来替换项目中的代码实现。

goconvey则是一个帮助我们组织和管理测试用例的框架,提供了ConveySo两种方法来搭配使用,支持树形结构方便构造各种场景。它本身是不会提供 mock 能力的,你可以基于goconvey来组织你的单测,在需要mock的场景下与gomonkey配合使用。

本文介绍的所有内容在我的专栏《Go项目搭建和整洁开发实战》中都有更详细的实战案例练习,为大家展示怎么给项目的核心业务逻辑做基于行为驱动的BDD测试。

goconvey 的安装和基本用法

在项目中使用goconvey 前需要先在项目依赖中添加goconvey,安装命令如下go get github.com/smartystreets/goconvey

我们先看一下goconvey官方给出的使用示例。

代码语言:javascript
代码运行次数:0
复制

package package_name

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

func TestSpec(t *testing.T) {

// Only pass t into top-level Convey calls
 Convey("Given some integer with a starting value", t, func() {
  x := 1
  Convey("When the integer is incremented", func() {
   x++
   Convey("The value should be greater by one", func() {
    So(x, ShouldEqual, 2)
   })
  })
 })
}

通过这个例子,正好说一下在使用goconvy的过程中需要注意的几个点:

  • 官方建议使用import .语法导入convey,import . "github.com/smartystreets/goconvey/convey",这样是为了方便大家直接使用 goconvey 中的各种定义,无需再像convey.Convey这样加包前缀。
  • Convey 函数是可以嵌套的,这样我们就可以构造出来一条测试的行为路径,帮助我们写出BDD风格的单测。
  • Convey 嵌套使用时函数的入参有区别
    • 最上层Convey 为Convey(description string, t *testing.T, action func())
    • 其他层级的嵌套 Convey 不需要传入 *testing.T,为Convey(description string, action func())

goconvey为我们提供了很多种ShouldXXX类断言方法在So()函数中使用,来比对前后两个参数之间的关系。

代码语言:javascript
代码运行次数:0
复制

func So(actual interface{}, assert Assertion, expected ...interface{}) {
 mustGetCurrentContext().So(actual, assert, expected...)
}

另外如果断言失败,goconvey底层会调用t.Fail()方法来告诉Go,你的go test就会失败,所以如果使用了goconvey,就不用在代码里手动调用t.Fail()了。

goconvey 实战演示

TestMain设置

首先需要在测试的入口 TestMain 中要加上SuppressConsoleStatisticsPrintConsoleStatistics,用于在测试完成后输出测试结果。

代码语言:javascript
代码运行次数:0
复制

func TestMain(m *testing.M) {
 // convey在TestMain场景下的入口
 SuppressConsoleStatistics()
 result := m.Run()
 // convey在TestMain场景下的结果打印
 PrintConsoleStatistics()
 os.Exit(result)
}

BDD行为驱动测试实战

下面我们使用goconvey 为 util 包的工具函数PasswordComplexityVerify编写测试,PasswordComplexityVerify的功能是用来检查用户注册账号时输入的密码是否满足复杂密码的要求。

代码语言:javascript
代码运行次数:0
复制

package util
func PasswordComplexityVerify(s string) bool {
 var (
  hasMinLen  = false
  hasUpper   = false
  hasLower   = false
  hasNumber  = false
  hasSpecial = false
 )
 ......
 return hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
}

使用Convey 为他编写的测试如下:

代码语言:javascript
代码运行次数:0
复制

func TestPasswordComplexityVerify(t *testing.T) {
 Convey("Given a simple password", t, func() {
  password := "123456"
  Convey("When run it for password complexity checking", func() {
   result := util.PasswordComplexityVerify(password)
   Convey("Then the checking result should be false", func() {
    So(result, ShouldBeFalse)
   })
  })
 })

 Convey("Given a complex password", t, func() {
  password := "123@1~356Wrx"
  Convey("When run it for password complexity checking", func() {
   result := util.PasswordComplexityVerify(password)
   Convey("Then the checking result should be true", func() {
    So(result, ShouldBeTrue)
   })
  })
 })
}

这里我们不仅仅有嵌套的 Convey,还有并列的 Convey。通过这种关系来表达各个不同测试之间的关联关系。在两个并列Convey中我们分别进行了正向和负向测试。

你可能问了,写单元测试就写呗,咋还冒出来个正向测试、负向测试呢?其实它们非常好理解:

  • 正向测试:提供正确的入参,期待被测对象返回正确的结果。
  • 负向测试:提供错误的入惨,期待被测对象返回错误的结果或者对应的异常。

结合我们在description参数中的描述,我们就可以建立起来类似BDD(行为驱动测试)的语义:

  • Given【给定某些初始条件】
    • Given a simple passowrd 给定一个简单密码
  • When 【当一些动作发生时】
    • When run it for password complexity checking 当对它进行复杂度检查时
  • Then 【结果应该是】
    • Then the checking result should be false 结果应该是 false

BDD测试中的描述信息通常使用的是Given、When、Then引导的状语从句,如果喜欢用中文写描述信息也要记得使用类似语境的句子。

你可能会问这么写了有什么用,咱们用命令来看看测试运行的效果,我们可以看到输出的测试结果会按照单测中Convey书写的层级,分层级显示。

GoConvey提供的断言方法

goconvey为我们提供了很多种ShouldXXX类断言方法在So()函数中使用,来比对前后两个参数之间的关系,主要有下面几类,大家用到的时候可以来这里参考。

一般相等类
代码语言:javascript
代码运行次数:0
复制

So(thing1, ShouldEqual, thing2)
So(thing1, ShouldNotEqual, thing2)
So(thing1, ShouldResemble, thing2) // 用于数组、切片、map和结构体相等
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)
数字数量比较类
代码语言:javascript
代码运行次数:0
复制

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)
包含类
代码语言:javascript
代码运行次数:0
复制

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
字符串类
代码语言:javascript
代码运行次数:0
复制

So("asdf", ShouldStartWith, "as")
So("asdf", ShouldNotStartWith, "df")
So("asdf", ShouldEndWith, "df")
So("asdf", ShouldNotEndWith, "df")
So("asdf", ShouldContainSubstring, "稍等一下") // optional 'expected occurences' arguments?
So("asdf", ShouldNotContainSubstring, "er")
So("adsf", ShouldBeBlank)
So("asdf", ShouldNotBeBlank)
panic类
代码语言:javascript
代码运行次数:0
复制

So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "") // or errors.New("something")
So(func(), ShouldNotPanicWith, "") // or errors.New("something")
类型检查类
代码语言:javascript
代码运行次数:0
复制

So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, "asdf")
时间和时间间隔类
代码语言:javascript
代码运行次数:0
复制

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())

总结

本文介绍的所有内容在我的专栏《Go项目搭建和整洁开发实战》中都有更详细的实战案例练习,为大家展示怎么给项目的核心业务逻辑做基于行为驱动的BDD测试。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 网管叨bi叨 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • goconvey 的安装和基本用法
  • goconvey 实战演示
    • TestMain设置
    • BDD行为驱动测试实战
  • GoConvey提供的断言方法
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档