365bet亚洲版登录-bet官网365入口

365bet亚洲版登录拥有超过百间客房,bet官网365入口的文化历经几十年的传承和积淀形成的核心内容获得业界广泛的认可,365bet亚洲版登录是目前信誉最高的娱乐场所,同国内外几百家网上内容供应商建立了合作关系。

GoConvey框架使用指南

在软件开发中,产品代码的正确性通过测试代码来保证,而测试代码的正确性谁来保证?答案是毫无争议的,肯定是程序员自己。这就要求测试代码必须足够简单且表达力强,让错误无处藏身。我们要有一个好鼻子,能够嗅出测试的坏味道,及时的进行测试重构,从而让测试代码易于维护。笔者从大量的编码实践中感悟道:虽然能写出好的产品代码的程序员很牛,但能写出好的测试代码的程序员更牛,尤其对于TDD实践。

要写出好的测试代码,必须精通相关的测试框架。对于Golang的程序员来说,至少需要掌握下面四个测试框架:

  • GoConvey
  • GoStub
  • GoMock
  • Monkey

笔者将通过多篇文章来阐述这四个测试框架,同时对于GoStub框架还将进行二次开发实践,以便高效的解决较复杂场景的打桩问题。

本文将主要介绍GoConvey框架的基本使用方法,从而指导读者更好的进行测试实践,最终写出简单优雅的测试代码。

GoConvey是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性。Golang虽然自带了单元测试功能,并且在GoConvey框架诞生之前也出现了许多第三方测试框架,但没有一个测试框架像GoConvey一样能够让程序员如此简洁优雅的编写测试代码。

在命令行运行下面的命令:

go get github.com/smartystreets/goconvey

运行时间较长,运行完后你会发现:

  1. 在$GOPATH/src目录下新增了github.com子目录,该子目录里包含了GoConvey框架的库代码
  2. 在$GOPATH/bin目录下新增了GoConvey框架的可执行程序goconvey

我们通过一个案例来介绍GoConvey框架的基本使用方法,并对要点进行归纳。

产品代码

我们实现一个判断两个字符串切片是否相等的函数StringSliceEqual,主要逻辑包括:

  • 两个字符串切片长度不相等时,返回false
  • 两个字符串切片一个是nil,另一个不是nil时,返回false
  • 遍历两个切片,比较对应索引的两个切片元素值,如果不相等,返回false
  • 否则,返回true

根据上面的逻辑,代码实现如下所示:

func StringSliceEqual(a, b []string) bool { if len != len { return false } if  !=  { return false } for i, v := range a { if v != b[i] { return false } } return true}

对于逻辑“两个字符串切片一个是nil,另一个不是nil时,返回false”的实现代码有点不好理解:

if  !=  { return false}

我们实例化一下a和b,即[]string{}和[]string,这时两个字符串切片的长度都是0,但肯定不相等。

测试代码

先写一个正常情况的测试用例,如下所示:

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

由于GoConvey框架兼容Golang原生的单元测试,所以可以使用go test -v来运行测试。打开命令行,进入$GOPATH/src/infra/alg目录下,运行go test -v,则测试用例的执行结果日下:

=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil ✔1 total assertion--- PASS: TestStringSliceEqual PASSok infra/alg 0.006s

上面的测试用例代码有如下几个要点:

  1. import goconvey包时,前面加点号".",以减少冗余的代码。凡是在测试代码中看到Convey和So两个方法,肯定是convey包的,不要在产品代码中定义相同的函数名
  2. 测试函数的名字必须以Test开头,而且参数类型必须为*testing.T
  3. 每个测试用例必须使用Convey函数包裹起来,它的第一个参数为string类型的测试描述,第二个参数为测试函数的入参(类型为*testing.T),第三个参数为不接收任何参数也不返回任何值的函数
  4. Convey函数的第三个参数闭包的实现中通过So函数完成断言判断,它的第一个参数为实际值,第二个参数为断言函数变量,第三个参数或者没有(当第二个参数为类ShouldBeTrue形式的函数变量)或者有(当第二个函数为类ShouldEqual形式的函数变量)

我们故意将该测试用例改为不过:

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

测试用例的执行结果日下:

=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil ✘Failures: * /Users/zhangxiaolong/Desktop/D/go-workspace/src/infra/alg/slice_test.go Line 45: Expected: false Actual: true1 total assertion--- FAIL: TestStringSliceEqual FAILexit status 1FAIL infra/alg 0.006s

我们再补充3个测试用例:

import ( "testing" . "github.com/smartystreets/goconvey/convey")func TestStringSliceEqual(t *testing.T) { Convey("TestStringSliceEqual should return true when a != nil && b != nil", t, func() { a := []string{"hello", "goconvey"} b := []string{"hello", "goconvey"} So(StringSliceEqual, ShouldBeTrue) }) Convey("TestStringSliceEqual should return true when a == nil && b == nil", t, func() { So(StringSliceEqual, ShouldBeTrue) }) Convey("TestStringSliceEqual should return false when a == nil && b != nil", t, func() { a := []string b := []string{} So(StringSliceEqual, ShouldBeFalse) }) Convey("TestStringSliceEqual should return false when a != nil && b != nil", t, func() { a := []string{"hello", "world"} b := []string{"hello", "goconvey"} So(StringSliceEqual, ShouldBeFalse) })}

从上面的测试代码可以看出,每一个Convey语句对应一个测试用例,那么一个函数的多个测试用例可以通过一个测试函数的多个Convey语句来呈现。

测试用例的执行结果如下:

=== RUN TestStringSliceEqual TestStringSliceEqual should return true when a != nil && b != nil ✔1 total assertion TestStringSliceEqual should return true when a == nil && b == nil ✔2 total assertions TestStringSliceEqual should return false when a == nil && b != nil ✔3 total assertions TestStringSliceEqual should return false when a != nil && b != nil ✔4 total assertions--- PASS: TestStringSliceEqual PASSok infra/alg 0.006s

Convey语句的嵌套

Convey语句可以无限嵌套,以体现测试用例之间的关系。需要注意的是,只有最外层的Convey需要传入*testing.T类型的变量t。我们将前面的测试用例通过嵌套的方式写另一个版本:

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, ShouldBeTrue) }) Convey("should return true when a == nil && b == nil", func() { So(StringSliceEqual, ShouldBeTrue) }) Convey("should return false when a == nil && b != nil", func() { a := []string b := []string{} So(StringSliceEqual, ShouldBeFalse) }) Convey("should return false when a != nil && b != nil", func() { a := []string{"hello", "world"} b := []string{"hello", "goconvey"} So(StringSliceEqual, ShouldBeFalse) }) })}

测试用例的执行结果如下:

=== 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 PASSok infra/alg 0.006s

可见,Convey语句嵌套的测试日志和Convey语句不嵌套的测试日志的显示有差异,笔者更喜欢这种以测试函数为单位多个测试用例集中显示的形式。

Web 界面

GoConvey不仅支持在命令行进行自动化编译测试,而且还支持在 Web 界面进行自动化编译测试。想要使用GoConvey的 Web 界面特性,需要在测试文件所在目录下执行goconvey:

$GOPATH/bin/goconvey

这时弹出一个页面,如下图所示:

图片 1goconvey-web.png

在 Web 界面中:

  1. 可以设置界面主题
  2. 查看完整的测试结果
  3. 使用浏览器提醒等实用功能
  4. 自动检测代码变动并编译测试
  5. 半自动化书写测试用例
  6. 查看测试覆盖率
  7. 临时屏蔽某个包的编译测试

Skip

针对想忽略但又不想删掉或注释掉某些断言操作,GoConvey提供了Convey/So的Skip方法:

  • SkipConvey函数表明相应的闭包函数将不被执行
  • SkipSo函数表明相应的断言将不被执行

当存在SkipConvey或SkipSo时,测试日志中会显式打上"skipped"形式的标记:

  • 当测试代码中存在SkipConvey时,相应闭包函数中不管是否为SkipSo,都将被忽略,测试日志中对应的符号仅为一个"⚠"
  • 当测试代码Convey语句中存在SkipSo时,测试日志中每个So对应一个"✔"或"✘",每个SkipSo对应一个"⚠",按实际顺序排列
  • 不管存在SkipConvey还是SkipSo时,测试日志中都有字符串"{n} total assertions (one or more sections skipped)",其中{n}表示测试中实际已运行的断言语句数

定制断言函数

我们先看一下So的函数原型:

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

第二个参数为assertion,它的原型为:

type assertion func(actual interface{}, expected ...interface{}) string

当assertion的返回值为""时表示断言成功,否则表示失败,GoConvey框架中的相关代码为:

const ( success = "" needExactValues = "This assertion requires exactly %d comparison values (you provided %d)." needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0).")

我们简单实现一个assertion函数:

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

我们仍然在slice_test文件中写一个简单测试:

func TestSummer(t *testing.T) { Convey("TestSummer", t, func() { So("summer", ShouldSummerBeComming, "comming") So("winter", ShouldSummerBeComming, "comming") })}

根据ShouldSummerBeComming的实现,Convey语句中第一个So将断言成功,第二个So将断言失败。我们运行测试,查看执行结果,符合期望:

=== 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 FAILexit status 1FAIL infra/alg 0.006s

Golang虽然自带了单元测试功能,但笔者建议大家使用已经成熟的第三方测试框架。本文主要介绍了GoConvey框架,通过文字结合代码示例讲解基本的使用方法,要点归纳如下:

  1. import goconvey包时,前面加点号".",以减少冗余的代码
  2. 测试函数的名字必须以Test开头,而且参数类型必须为*testing.T
  3. 每个测试用例必须使用Convey函数包裹起来,推荐使用Convey语句的嵌套,即一个函数有一个测试函数,测试函数中嵌套两级Convey语句,第一级Convey语句对应测试函数,第二级Convey语句对应测试用例
  4. Convey语句的第三个参数习惯以闭包的形式实现,在闭包中通过So语句完成断言
  5. 使用GoConvey框架的 Web 界面特性,作为命令行的补充
  6. 在适当的场景下使用SkipConvey函数或SkipSo函数
  7. 当测试中有需要时,可以定制断言函数

至此,希望读者已经掌握了GoConvey框架的基本用法,从而可以写出简单优雅的测试代码。

然而,事情并没有这么简单!试想,如果在被测函数中调用了底层os包的stat函数,我们该如何写测试代码?其实答案也并不复杂,我们将在下一篇文章中揭晓。

本文由365bet亚洲版登录发布于计算机网络,转载请注明出处:GoConvey框架使用指南

您可能还会对下面的文章感兴趣: