一、单元测试是什么

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

二、单元测试的意义

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. SpringBoot整合openoffice实现word文档的读取和导入及报错处理

    先安装openoffice4 Linux系统安装参考:https://www.cnblogs.com/pxblog/p/11622969.html Windows系统安装参考:https://www. ...

  2. 【九度OJ】题目1433:FatMouse 解题报告

    [九度OJ]题目1433:FatMouse 解题报告 标签(空格分隔): 九度OJ http://ac.jobdu.com/problem.php?pid=1433 题目描述: FatMouse pr ...

  3. Soldier and Traveling

    B. Soldier and Traveling Time Limit: 1000ms Memory Limit: 262144KB 64-bit integer IO format: %I64d   ...

  4. Shortest Path(hdu5636)

    Shortest Path  Accepts: 40  Submissions: 610  Time Limit: 4000/2000 MS (Java/Others)  Memory Limit: ...

  5. 【因果推断经典论文】Direct and Indirect Effects - Judea Pearl

    Direct and Indirect Effects Author: Judea Pearl UAI 2001 加州大学洛杉矶分校 论文链接:https://dl.acm.org/doi/pdf/1 ...

  6. 【OpenXml】Pptx的边框虚线转为WPF的边框虚线

    安装Openxml sdk 首先,我们先安装nuget的需要的有关的Openxml sdk,我们开源了解析pptx的Openxml拍平层,下面两种方式都可以安装: nuget包管理器控制台: Inst ...

  7. Understanding and Improving Fast Adversarial Training

    目录 概 主要内容 Random Step的作用 线性性质 gradient alignment 代码 Andriushchenko M. and Flammarion N. Understandin ...

  8. Not All Samples Are Created Equal: Deep Learning with Importance Sampling

    目录 概 主要内容 "代码" Katharopoulos A, Fleuret F. Not All Samples Are Created Equal: Deep Learnin ...

  9. oracle函数listagg使用

    作用 可以实现将多列记录聚合为一列记录,实现数据的压缩 语法结构 listagg(measure_expr,delimiter) within group ( order by order_by_cl ...

  10. linux脚本重启java服务

    !/bin/bashpid=$(ps -ef | grep zwdatatransfer-1.0.0.jar | grep -v 'grep' | awk '{print $2}')kill -9 $ ...