一、单元测试是什么

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

二、单元测试的意义

1.提高代码质量:编写测试用例会迫使开发人员仔细思考代码的设计和必须完成的工作,进而会改善代码质量。

2.简化调试过程:开发人员可以测试单个模块的功能,不依赖外部工程和数据源。

3.保证重构正确:单元测试可以有效地测试某个程序模块的行为,是未来重构代码的信心保证。

4.尽早发现问题:单元测试由功能对应的开发者完成,测试考虑的范围更加全面,可以尽早发现大部分问题。

三、Golang单元测试框架

3.1 Golang内置testing包

3.1.1 简单的测试

简单测试用例定义如下:

func TestXXXX(t *testing.T) {
// ...
}
  • Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。
  • 函数名必须以 Test 开头,后面一般跟待测试的函数名
  • 参数为 t *testing.T

3.1.2 Benchmark 基准测试

基准测试用例的定义如下:

func BenchmarkName(b *testing.B){
// ...
}
  • 函数名必须以 Benchmark 开头,后面一般跟待测试的函数名
  • 参数为 b *testing.B。

3.1.3 运行测试用例

# 匹配当前目录下*_test.go命令的文件,执行每一个测试函数
go test -v # 执行 calc_test.go 文件中的所有测试函数
go test -v calc_test.go calc.go # 指定特定的测试函数(其中:-count=1用于跳过缓存)
go test -v -run TestAdd calc_test.go calc.go -count=1 # 执行基准测试
go test -benchmem -bench .

3.1.4 简单的测试示例

当前 package 有 calc.go 一个文件,我们想测试 calc.go 中的 Add 和 Substract 函数,那么应该新建 calc_test.go 作为测试文件。

testing/
| -- calc.go
| -- calc_test.go

calc.go的代码如下:

package testing

func Add(a, b int) int {
return a + b
} func Substract(a, b int) int {
return a - b
}

calc_test.go的代码如下

package testing

import (
"testing"
) func TestAdd(t *testing.T) {
a := 2
b := 3
ret := Add(a, b)
if ret != a+b {
t.Fatalf("Expect:%d Actual:%d", a+b, ret)
}
} func TestSubtests(t *testing.T) {
// 嵌套
t.Run("TestSubstract", func(t *testing.T) {
a := 3
b := 2
ret := Substract(a, b)
if ret != a-b {
t.Fatalf("Expect:%d Actual:%d", a-b, ret)
}
})
}

3.2 GoConvey测试框架

GoConvey 是个相当不错的 Go 测试工具,支持 go test。可直接在终端窗口和浏览器上使用。

3.2.1.安装依赖:

go get github.com/smartystreets/goconvey

3.2.2.测试用例

package/
| --calc.go
| --calc_test.go

calc.go的代码如下:

package goconvey

import "errors"

func Add(a, b int) int {
return a + b
} func Subtract(a, b int) int {
return a - b
} func Multiply(a, b int) int {
return a * b
} func Division(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("被除数不能为 0")
}
return a / b, nil
}

calc_test.go的代码如下:

package goconvey

import (
"testing" . "github.com/smartystreets/goconvey/convey"
) func Test_Add(t *testing.T) {
Convey("将两数相加", t, func() {
So(Add(1, 2), ShouldEqual, 3)
})
} func Test_Substract(t *testing.T) {
// 忽略断言
SkipConvey("将两数相减", t, func() {
So(Subtract(2, 1), ShouldEqual, 1)
})
} func Test_Multiply(t *testing.T) {
Convey("将两数相乘", t, func() {
So(Multiply(3, 2), ShouldEqual, 6)
})
} func Test_Division(t *testing.T) {
Convey("将两数相除", t, func() {
// 嵌套
Convey("除以非 0 数", func() {
num, err := Division(10, 2)
So(err, ShouldBeNil)
So(num, ShouldEqual, 5)
// 忽略断言
SkipSo(num, ShouldNotBeNil)
}) Convey("除以 0", func() {
_, err := Division(10, 0)
So(err, ShouldNotBeNil)
})
})
}

3.2.3. 运行测试

1.终端窗口使用:go test -v

2.Web浏览器使用:在相应的目录执行 goconvey,然后访问http://localhost:8080

3.3 testify测试框架

3.3.1. 安装依赖

go get github.com/stretchr/testify

3.3.2 测试用例

import (
"testing"
"github.com/stretchr/testify/assert"
) func Test_assert(t *testing.T) {
a := 2
b := 3
assert.Equal(t, a+b, 5, "They should be equal")
}

require包提供与assert包相同的全局函数,相比assert没有返回值,而是终止当前测试

import (
"testing"
"github.com/stretchr/testify/require"
) func Test_require(t *testing.T) {
var name = "dazuo"
var age = 24
require.Equal(t, "dazuo", name, "they should be equal")
require.Equal(t, 24, age, "they should be equal")
}

mock Package提供了一种轻松编写模拟对象的机制,可以在编写测试代码时代替实际对象使用模拟对象。

package testify

import (
"fmt"
"testing" "github.com/stretchr/testify/mock"
) type Storage interface {
Store(key, value string) (int, error)
Load(key string) (string, error)
} // 测试用例,当真实对象不可用时,使用mock对象代替
type mockStorage struct {
mock.Mock
} func (ms *mockStorage) Store(key, value string) (int, error) {
args := ms.Called(key, value)
return args.Int(0), args.Error(1)
} func (ms *mockStorage) Load(key string) (string, error) {
args := ms.Called(key)
return args.String(0), args.Error(1)
} func Test_mock(t *testing.T) {
mockS := &mockStorage{}
mockS.On("Store", "name", "dazuo").Return(20, nil).Once() var storage Storage = mockS
i, e := storage.Store("name", "dazuo")
if e != nil {
panic(e)
}
fmt.Println(i)
}

四、Stub/Mock框架

4.1 GoStub框架

4.1.1.安装依赖

go get github.com/prashantv/gostub

4.1.2.使用场景

  • 为全局变量打桩
  • 为函数打桩
  • 为过程打桩(当一个函数没有返回值时,该函数我们一般称为过程)

4.1.3.测试示例

import (
"fmt" "github.com/prashantv/gostub"
) // 1.为全局变量打桩
var counter = 100 func stubGlobalVariable() {
stubs := gostub.Stub(&counter, 200)
defer stubs.Reset()
fmt.Println("Counter:", counter)
} // 2.为函数打桩
var Exec = func() {
fmt.Println("Exec")
} func stubFunc() {
stubs := gostub.Stub(&Exec, func() {
fmt.Println("Stub Exec")
})
Exec()
defer stubs.Reset()
} // 3.为过程打桩(当一个函数没有返回值时,该函数我们一般称为过程。很多时候,我们将资源清理类函数定义为过程。)
var CleanUp = cleanUp func cleanUp(val string) {
fmt.Println(val)
} func stubPath() {
stubs := gostub.StubFunc(&CleanUp)
defer stubs.Reset()
CleanUp("Hello go")
} func main() {
//stubGlobalVariable()
//stubFunc()
stubPath()
}

4.2 GoMock框架

4.2.1 GoMock简介

gomock 是官方提供的 mock 框架,同时还提供了 mockgen 工具用来辅助生成测试代码。

使用如下命令即可安装:

go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen

4.2.2 测试示例

1.新建db.go文件,代码如下:

// db.go
type DB interface {
Get(key string) (int, error)
} func GetFromDB(db DB, key string) int {
if value, err := db.Get(key); err != nil {
return value
}
return -1
}

2.使用 mockgen 生成 db_mock.go

mockgen -source=db.go -destination=db_mock.go -package=PACKAGE_NAME

3.写测试用例 TestGetFromDB

import (
"errors"
"testing" "github.com/golang/mock/gomock"
) func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用 m := NewMockDB(ctrl)
// 打桩
m.EXPECT().Get(gomock.Eq("Tom")).Return(0, errors.New("not exist")) if v := GetFromDB(m, "Tom"); v != -1 {
t.Fatal("expected -1, but got", v)
}
}

4.3 gomonkey框架

4.3.1 安装依赖

go get github.com/agiledragon/gomonkey

4.3.2 测试用例

example_test.go的代码如下:

import (
"fmt"
"testing" "github.com/agiledragon/gomonkey"
"github.com/smartystreets/goconvey/convey"
) // 假设networkFunc是一个网络调用
func networkFunc(a, b int) int {
return a + b
} // 本地单测一般不会进行网络调用,所以要mock住networkFunc
func Test_MockNetworkFunc(t *testing.T) {
convey.Convey("123", t, func() {
p := gomonkey.NewPatches()
defer p.Reset() p.ApplyFunc(networkFunc, func(a, b int) int {
fmt.Println("in mock function")
return a + b
})
_ = networkFunc(10, 20)
})
}

4.3.3 运行测试

如果启用了内联,将无法mock,可以通过添加命令行参数:

  • go1.10以下的:-gcflags=-l
  • go1.10以上的:-gcflags=all=-l
go test -v example_test.go -gcflags=all=-l

五、参考资料

1.Go Test 单元测试简明教程

2.GoConvey测试框架使用指南

3.Go Mock (gomock)简明教程

4.Inlining optimisations in Go

Golang单元测试框架整理的更多相关文章

  1. 基于.NET平台常用的框架整理(转)

    自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的 学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开源的组件,就目前想到的先整理于此,如果再想到, ...

  2. 【转】基于.NET平台常用的框架整理

    自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累 了一些开源的组件,就目前想到的先整理于此,如果再想到, ...

  3. 基于.NET平台常用的框架整理

    自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开源的组件,就目前想到的先整理于此,如果再想到,就 ...

  4. .NET平台常用的框架整理

    基于.NET平台常用的框架整理 DotNet | 2016-03-31 17:13 (点击上方蓝字,可快速关注我们) 来源:天使不哭 链接:http://www.cnblogs.com/hgmyz/p ...

  5. 基于.NET平台常用的框架整理【转】

    转:http://www.cnblogs.com/hgmyz/p/5313983.html 自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产 ...

  6. ( 转)基于.NET平台常用的框架整理

    自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开源的组件,就目前想到的先整理于此,如果再想到,就 ...

  7. 基于.NET平台常用的框架整理 (转)

    http://www.cnblogs.com/hgmyz/p/5313983.html 自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了 ...

  8. [转]基于.NET平台常用的框架整理

    自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大开发工具,极小的学习曲线,让我对这个平台产生了浓厚的兴趣,在工作和学习中也积累了一些开源的组件,就目前想到的先整理于此,如果再想到,就 ...

  9. 基于.NET平台常用的框架整理<转载>

    转载来自:http://www.cnblogs.com/hgmyz/p/5313983.html 基于.NET平台常用的框架整理   自从学习.NET以来,优雅的编程风格,极度简单的可扩展性,足够强大 ...

随机推荐

  1. C# Dispose模式

    需要明确一下C#程序(或者说.NET)中的资源.简单的说来,C#中的每一个类型都代表一种资源,而资源又分为两类: 托管资源:由CLR管理分配和释放的资源,即由CLR里new出来的对象: 非托管资源:不 ...

  2. JAVA发送POST请求携带JSON格式字符串参数

    import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.htt ...

  3. 【LeetCode】1110. Delete Nodes And Return Forest 解题报告 (C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...

  4. spoj - ACTIV - Activities

    ACTIV - Activities Ana likes many activities. She likes acrobatics, alchemy, archery, art, Arabic da ...

  5. Essentially No Barriers in Neural Network Energy Landscape

    目录 梗概 主要内容 path的定义 path的逼近 Mechanical Model Nudged Elastic Band 局部最优 Draxler F, Veschgini K, Salmhof ...

  6. element message多次点击出现多个提示框,如何显示一个或者在同一位置显示

    /* message在同一位置弹出 */ /* reset elementUI message */ .el-message { top: 20px !important; } .el-message ...

  7. [opencv]统计每个像素值的数目

    int histo[256] = { 0 };//直方图统计每个像素值的数目 int width = img.cols, height = img.rows; int num_of_pixels = ...

  8. IT6563|4LAN DP转HDMI 4K60HZ /2.0转换方案|CS5263替代IT6563

    IT6563是一款4通道DisplayPort1.2到HDMI 4K60Hz转换器,IT6563结合DisplayPort接收机和HDMI发射机,通过转换功能支持DisplayPort输入和HDMI输 ...

  9. 大厂必问的JVM面试题

    本文目录: 讲一下JVM内存结构? 程序计数器 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 Java对象的定位方式 说一下堆栈的区别? 什么情况下会发生栈溢出? 类文件结构 什么是类加载 ...

  10. for update未提交导致锁表

    select for update 是为了在查询时,避免其他用户以该表进行插入,修改或删除等操作,造成表的不一致性. 应用场景: 什么时候需要使用for update?就是那些需要业务层面数据独占时, ...