一、单元测试是什么

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

二、单元测试的意义

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. oracle同义词创建(synonym)

    oracle同义词创建(synonym)   在现在的项目中会有很多接口,数据来源也可能是不同数据库或者是不同的用户下的表,给访问该表带来了一定的麻烦.这个时候就可以使用同义词来简化. 同义词的语法是 ...

  2. 阿里云ilogtail收集自建Kubernetes容器日志文件

    背景 1,k8s属于自建. 2,需要收集应用服务容器里面指定目录的日志. 3,计划收集所有私有云php和nginx日志. 4,日志格式化处理. 思考 1,一个私有云一个Project,还是统一放入一个 ...

  3. java 多线程:线程安全问题,示例DateFormat多线程执行冲突解决方案ThreadLocal、方法内变量

    SimpleDateFormat多线程中执行报错 java.lang.NumberFormatException: For input string: ""   import ja ...

  4. 什么是协程?与线程和进程对比优劣在哪?gevent协程示例代码

      协程 协程,又称微线程,纤程.英文名Coroutine..一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在 ...

  5. uniapp框架如何实现仿微信相册:图视频过滤、相册选择功能

    今天我们分享基于uniapp + vue实现仿微信相册插件实例,该插件完全还原了微信相册的功能 1: 相册选择 2: 图片,视频类型过滤 3: 自定义相册界面UI 技术实现 开发环境:Hbuilder ...

  6. video标签实现多个视频循环播放

    <head> <!-- If you'd like to support IE8 (for Video.js versions prior to v7) --> </he ...

  7. 价格BigDecimal的加减乘除、小数四舍五入、比较

    num2必须改用传入String类型 //加法 BigDecimal result1 = num1.add(num2); //减法 BigDecimal result2 = num1.subtract ...

  8. xcode使用spdlog(1.7)总结

    !!版权声明:本文为博主原创文章,版权归原文作者和博客园共有,谢绝任何形式的 转载!! 作者:mohist 注意️ 请选择对c++11支持完善的编译器, 因为spdlog一直更新. 本文演示环境: m ...

  9. nim_duilib(15)之duilib属性列表.xml

    Note 为了更加方便查看duilib的属性(github有时候打不开),特此记录. 阅读本文,可以知道控件有哪些属性,可以写在xml文件中.个别需要结合源码一起看 from here 原文 < ...

  10. 【LeetCode】561. Array Partition I 解题报告(Java & Python)

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