1、单元测试是什么

单元测试可以检查我们的代码能否按照预期进行,代码逻辑是否有问题,以此可以提升代码质量。简单来说单元测试就是针对某一个函数方法进行测试,我们要先测试正确的传值与获取正确的预期结果,然后再添加更多测试用例,得出多种预期结果。尽可能达到该方法逻辑没有问题,或者问题都能被我们预知到。这就是单元测试的好处。

2、Golang 是怎么写单元测试的?

很简单!Golang 本身对自动化测试非常友好,并且有许多优秀的测试框架支持,非常好上手。文中将以新手的角度,快速上手一一实践给大家感受 Go 单元测试的魅力。

3、写一个最简单的 Test 吧!

先来了解一下 Go 官方的 testing 包

要编写一个测试文件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx 函数,如上所述。 将该文件放在与被测试文件相同的包中。该文件将被排除在正常的程序包之外,但在运行 go test 命令时将被包含。

测试函数的签名必须接收一个指向 testing.T 类型的指针,并且不能返回任何值。函数名最好是 Test + 要测试的方法函数名。


一个简单的例子

这个代码将输出一句 “Hello, world”

测试

测试结果

说明我们的测试通过了,返回的值与我们预期的值是相同的。

现在尝试把预期结果修改一下

want := "xdcute.com"

测试结果

此时提示测试不通过,得到的值与预期的值不相同。

这就是一个最简单的测试写法,我们可以进行正确或错误的测试。


这里介绍几个常用的参数:

-bench:regexp 执行相应的 benchmarks,例如 -bench= (基准测试)

-cover:开启测试覆盖率

-run :regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数;

-v :显示测试的详细命令


GoConvey 测试框架

在前面,我们判断预期结果都是用 if…else 之类的判断语句,如果面对更庞大的测试,有更多的测试用例,可能需要思考更多逻辑判断,加大代码长度与复杂度,不便于编写与管理。所以我们需要用到更好的测试框架来增强测试编写。

GoConvey 是一款针对 Golang 的测试框架,它可以更好的管理和运行测试用例,而且又很丰富的断言函数,能够写出更完善的测试用例,并且还有 web 界面,是极其常用的测试框架。

安装

基本使用方法


下面是一个基本四则运算(加、减、乘、除)的代码:

下面为这 4 个函数分别书写单元测试:

TestTestAdd*testing.TConvey*testing.T
ConveyTestDivisionConveySoShouldBeNilShouldEqualShouldNotBeNil

运行测试

go test -v

我们可以看到,输出结果调理非常清晰,单元测试的代码写起来也非常优雅。那么,这就是全部吗?当然不是。GoConvey 还有非常舒适的 Web 界面提供给开发者来进行自动化的编译测试工作。

GoConvey断言

Web 界面

想要使用 GoConvey 的 Web 界面特性,需要在相应目录下执行 goconvey,然后打开浏览器,访问 http://localhost:8080 ,就可以看到下以下界面:

表格驱动测试

结合表格测试可以写多个测试用例。testing 本身也可以写表格测试,这里使用 Testify 演示。

测试代码


func TestCheckUrl3(t *testing.T) {

assert := assert.New(t)

var tests = []struct {

input string

expected bool

}{

{"xdcute.com", true},

{"xxx.com", false},

}

for _, test := range tests {

fmt.Println(test.input)

assert.Equal(CheckUrl(test.input), test.expected)

}

}

这就是关于 Testify 的快速上手,关于它的 mock 功能,将在后面引入 mock 概念后再介绍。


HttpMock - 模拟 http 请求

在 web 项目中,大多接口是处理 http 请求(post、get 之类的),可以利用官方自带的 http 包来进行模拟请求。

假如有一个 HttpGetWithTimeOut 方法,内部逻辑会有一个 get 请求,最后返回内容。我们在测试环境中,是访问不到它发起的 get 请求的 url 的,此时就可以模拟 http 请求来写测试。

代码示例:


func TestHttpGetWithTimeOut(t *testing.T) {

Convey("TestHttpGetWithTimeOut", t, func() {

Convey("TestHttpGetWithTimeOut normal", func() {

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

w.WriteHeader(http.StatusOK)

w.Write([]byte("TestHttpGetWithTimeOut success!!"))

if r.Method != "GET" {

t.Errorf("Except 'Get' got '%s'", r.Method)

}

if r.URL.EscapedPath() != "/要访问的url" {

t.Errorf("Expected request to '/要访问的url', got '%s'", r.URL.EscapedPath())

}

}))

api := ts.URL

defer ts.Close()

var header = make(map[string]string)

HttpGetWithTimeOut(api, header, 30)

})

httptest.NewServer():创建一个 http 请求

http.ResponseWriter:响应体

http.Request:请求体


这段代码中,通过 w 来设置返回的头内容与写入内容,通过 r 来设置请求方法和请求的 url。

最后将模拟好的请求,传参对应方法。


SqlMock - 模拟数据库请求

特点:

1. 模拟任何实现了 sql/driver 接口的 db 驱动,无需关注 db 连接。


使用参考:

构建模拟 sql


db, mock, err = sqlmock.New() // mock sql.DB

defer db.Close()

执行查询语句


mock.ExpectQuery(sqlSelectAll).WillReturnRows(sqlmock.NewRows(nil))

有更多调用方法请查看官方文档


GoMonkey - 强大的打桩框架

特点:

1. 直接在方法级别上进行 mock

(在运行时通过汇编语句重写可执行文件,将待打桩函数或方法的实现跳转到桩实现)

在编译阶段直接替换掉真的函数代码部分

2. 非线程安全,请勿用于并发测试


使用:

PatchInstanceMethod() 对于方法

在使用前,先要定义一个目标类的指针变量 x

第一个参数是 reflect.TypeOf (x)

第二个参数是字符串形式的函数名

返回值是一个 PatchGuard 对象指针,主要用于在测试结束时删除当前的补丁


var e *Etcd

guard := PatchInstanceMethod(reflect.TypeOf(e), "Get", func(_ *Etcd, _ string) []string {

return []string{"task1", "task5", "task8"}

})

defer guard.Unpatch()

Patch() 对于过程

当一个函数没有返回值时,该函数我们一般称为过程。很多时候,我们将资源清理类函数定义为过程。


guard := Patch(DestroyResource, func(_ string) {

})

defer guard.Unpatch()

对于函数


第一个参数是目标函数的函数名

第二个参数是桩函数的函数名,习惯用法是匿名函数或闭包

返回值是一个 PatchGuard 对象指针,主要用于在测试结束时删除当前的补丁


func TestExec(t *testing.T) {

Convey("test has digit", t, func() {

Convey("for succ", func() {

outputExpect := "xxx-vethName100-yyy"

guard := Patch(osencap.Exec, func(_ string, _ ...string) (string, error) {

return outputExpect, nil

})

defer guard.Unpatch()

output, err := osencap.Exec(any, any)

So(output, ShouldEqual, outputExpect)

So(err, ShouldBeNil)

})

})

}