Go单元测试及框架使用

这篇具有很好参考价值的文章主要介绍了Go单元测试及框架使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Go自带测试框架

单元测试

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

简单测试用例定义如下:

func TestXXXX(t *testing.T) {
     // ...
 }

在goland中,编写好方法后,右键Generate->Test for funtion, 可自动生成单元测试代码

Go单元测试及框架使用

Go单元测试及框架使用

生成的代码如下:

Go单元测试及框架使用

需要在TODO里填上单元测试参数,含义如下:

name:单元测试名称
 args:方法入参
 want:希望的出参

测试结果:

Go单元测试及框架使用

日志打印

Log() 打印日志
Logf() 格式化打印日志
Error() 打印错误日志
Errorf() 格式化打印错误日志
Fatal() 打印致命日志, 会直接中断当前测试方法
Fatalf() 格式化打印致命日志,会直接中断当前测试方法
Fail() 标记失败,但继续执行当前测试函数
FailNow() 失败,立即终止当前测试函数执行
Skip() 跳过当前函数,通常用于未完成的测试用例

基准测试

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

func BenchmarkName(b *testing.B){
     // ...
 }
  • 函数名必须以 Benchmark 开头,后面一般跟待测试的函数名
  • 参数为 b *testing.B
  • goland中没有自动基准测试的方法,需要按照规则手动自己加

原方法

func sayHi(name string)  string{
   return "hi," + name
 }

基准测试代码

func BenchmarkSayHi(b *testing.B) {
   for i := 0; i < b.N; i++ {
     sayHi("Max")
   }
 }

Goland中执行基准测试

Go单元测试及框架使用

命令行中执行基准测试

go test helloworld_test.go

结果解读

当测试开始时,b.N的值被设置为1,执行后如果没有超过默认执行时间上限(默认为1秒),则加大b.N的值,按某种规则一直递增,直到执行时间等于或超过上限,那么就用这一次的b.N的值,做为测试的最终结果

BenchmarkSayHi-12       81593520                14.71 ns/op
 PASS
 ok      zh.com/internal/benchmark_test  2.347s
  • BenchmarkSayHi-12表示执行 BenchmarkSayHi 时,所用的最大P的数量为12
  • 81593520: 表示sayHi()方法在达到这个执行次数时,等于或超过了1秒
  • 14.71 ns/op: 表示每次执行sayHi()所消耗的平均执行时间
  • 2.347s:表示测试总共用时

测试总时间的计算

既然81593520表示1秒或大于1秒时执行的次数,那么测试总时间用时却是2.386s,超出了不少,这是为什么呢

在测试中加入b.Log(“NNNNN:”, b.N),再执行基准测试,并加入-v,打印测试中的日志

func BenchmarkSayHi(b *testing.B) {
    for i := 0; i < b.N; i++ {
       SayHi("Max")
   }
    b.Log("NNNNN:", b.N)
}
go test -v -bench=. -run=^$ gott/SayHi
 BenchmarkSayHi
     fun1_test.go:26: NNNNN: 1
     fun1_test.go:26: NNNNN: 100
     fun1_test.go:26: NNNNN: 10000
     fun1_test.go:26: NNNNN: 1000000
     fun1_test.go:26: NNNNN: 3541896
     fun1_test.go:26: NNNNN: 4832275
 BenchmarkSayHi-4         4832275               236.8 ns/op
 PASS
 ok      gott/SayHi      2.395s

可以看到b.Log(“NNNNN:”, b.N)被执行了6次,这证明了之前提到的,测试会对b.N依次递增,直到执行时间等于或超过上限。在对BenchmarkSayHi()运行基准测试时,N值依次按1,100,10000,1000000,3541896,4832275递增,直到执行次数为4832275时,执行时间等于或超过了上限。

同时也说明BenchmarkSayHi()一共被调用了6次,每次运行BenchmarkSayHi()都要消耗一定的时间,所以测试总耗时为这6次调用时间之和,2.395s,超过了1秒

benchtime 标记

可以通过-benchtime标记修改默认时间上限,比如改为3秒

go test -v -bench=. -benchtime=3s -run=^$ gott/SayHi
 goos: darwin
 goarch: amd64
 pkg: gott/SayHi
 BenchmarkSayHi
     fun1_test.go:31: NNNNN: 1
     fun1_test.go:32: /Users/ga/m/opt/go/go_root
     fun1_test.go:31: NNNNN: 100
     fun1_test.go:32: /Users/ga/m/opt/go/go_root
     fun1_test.go:31: NNNNN: 10000
     fun1_test.go:32: /Users/ga/m/opt/go/go_root
     fun1_test.go:31: NNNNN: 1000000
     fun1_test.go:32: /Users/ga/m/opt/go/go_root
     fun1_test.go:31: NNNNN: 15927812
     fun1_test.go:32: /Users/ga/m/opt/go/go_root
 BenchmarkSayHi-4    15927812         223.4 ns/op
 PASS
 ok    gott/hello  3.802s

还可以设置具体的探索次数最大值,格式为-benchtime=Nx

go test gott/hello -run=^$ -bench=BenchmarkHello -benchtime=50x
 goos: darwin
 goarch: amd64
 pkg: gott/hello
 BenchmarkHello-4              50              2183 ns/op
 --- BENCH: BenchmarkHello-4
     fun1_test.go:35: NNNNN: 1
     fun1_test.go:36: /Users/ga/m/opt/go/go_root
     fun1_test.go:35: NNNNN: 50
     fun1_test.go:36: /Users/ga/m/opt/go/go_root
 PASS
 ok      gott/hello      0.011s

b.N的值被设置为50,函数运行了50次

benchmem 标记

可以通过-benchmem标记查看内存使用信息

go test -bench=. -run=none -benchmem

go test gott/hello -run=^$ -bench=BenchmarkHello -benchmem
go test gott/hello -run=^$ -bench=BenchmarkHello -benchmem
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4         5137456               223.1 ns/op            32 B/op          2 allocs/op
--- BENCH: BenchmarkHello-4
fun1_test.go:35: NNNNN: 1
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 100
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 10000
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 1000000
fun1_test.go:36: /Users/ga/m/opt/go/go_root
fun1_test.go:35: NNNNN: 5137456
fun1_test.go:36: /Users/ga/m/opt/go/go_root
... [output truncated]
PASS
ok      gott/hello      1.399s
  • 32 B/op:平均每次迭代内存分配的字节数
  • 2 allocs/op:平均每次迭代内存分配的次数

平均每次迭代计算的依据应该使用的是 b.N=5137456迭代次数

基准测试的用途

一般用于对比两个不同的操作所消耗的时间,如

  • 渐近增长函数的运行时间一个函数需要1ms处理1,000个元素,处理10000或1百万将需要多少时间呢
  • I/O缓存该设置为多大基准测试可以帮助我们选择在性能达标情况下所需的最小内存
  • 确定哪种算法更好

覆盖率测试

运行run with coverage

Go单元测试及框架使用

结果解读

右侧会展示覆盖率,左侧绿色为单元测试已覆盖到的代码,红色为未覆盖的代码

Go单元测试及框架使用

example测试

样例测试比较像平时在一些算法刷题平台(比如LeetCode)的题目的一些例子,样例测试以Example打头,其逻辑也很简单,就是使用fmt.Println输出该测试用例的返回结果,然后在函数体的末尾使用如图的注释,一一对应每个fmt.Println的输出:

Go单元测试及框架使用

如果输出和注释不能对应上则不通过

模糊测试

go版本要求

Fuzz模糊测试需要Go 1.18 Beta 1或以上版本的泛型功能

测试代码

package fuzz_test

import (
	"fmt"
	"testing"
	"unicode/utf8"
)

func Reverse(s string) string {
	b := []byte(s)
	for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}
	return string(b)
}

func FuzzReverse(f *testing.F) {
	testcases := []string{"Hello, world", "!12345"}
	for _, tc := range testcases {
		f.Add(tc) // Use f.Add to provide a seed corpus
	}
	f.Fuzz(func(t *testing.T, orig string) {
		rev := Reverse(orig)
		fmt.Printf("original->:%s", orig)
		fmt.Printf("after->:%s", rev)

		doubleRev := Reverse(rev)
		if orig != doubleRev {
			t.Errorf("Before: %q, after: %q", orig, doubleRev)
		}
		if utf8.ValidString(orig) && !utf8.ValidString(rev) {
			t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
		}
	})
}

测试结果

Go单元测试及框架使用

第三方框架

总体介绍
框架名 使用说明 优点 缺点
testing 如上 go官方原生测试框架,简单好用 断言不够友好,需要大量if else可以配合testify的assert使用
testify 1. 和 go test 无缝集成,直接使用该命令运行2. 支持断言,写法更简便3. 支持 mock & suite功能 mock的功能不够强大,需要配合其他mock框架使用
GoConvey 1. 能够使用 go test 来运行测试2. 支持断言,写法更简便3. 支持通过浏览器查看测试结果4. 支持嵌套,可以分组 1. 写法并不简便,主要多了个通过浏览器查看测试结果,个人觉得不是很有使用的必要2. 单元测试应该尽可能简单可维护,嵌套分组等功能太复杂,不利于维护
结论

建议采用testing+testify,goland支持自动生成testing单测模板,加上testify丰富的断言够用了

mock框架

golang中常用的stub/mock框架

GoStub Gomonkey Gomock
轻量级打桩框架 运行时重写可执行文件,类似热补丁 官方提供的mock框架,功能强大
支持为全局变量,函数打桩 性能强大,使用方便 mockgen 工具可自动生成mock代码;支持mock所有接口类型
需要改造原函数,使用不方便;性能不强 支持对变量,函数,方法打桩,支持打桩序列 可以配置调用次数,调用顺序,根据入参动态返回结果等
不是并发安全的;使用可能根据版本不同需要有些额外配置工作 只支持接口级别mock,不能mock普通函数
结论

建议采用Gomonkey. GoStub很多功能不支持,GoMock每次编写完需要重新generate生成代码,不太方便

其他特定领域mock工具

框架名 说明
GoSqlMock sqlmock包,用于单测中mock db操作
miniredis 纯go实现的用于单元测试的redis server。它是一个简单易用的、基于内存的redis替代品,它具有真正的TCP接口。当我们为一些包含 Redis 操作的代码编写单元测试时可以使用它来 mock Redis 操作
Httptest Golang官方自带,生成一个模拟的http server.主要使用的单测场景是:已经约定了接口,但是服务端还没实现

其他

goland中没有类似TestMe的Go单元测试插件,可以考虑实现一个

mock工具GoMock使用

GoMock

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

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

简单的使用方法:

// 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
 }
 复制代码

有一个DB接口,使用mockgen产生一个mock对象

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

下面是自动生成的代码

// Code generated by MockGen. DO NOT EDIT.
 // Source: db.go
 
 // Package mian is a generated GoMock package.
 package mian
 
 import (
    reflect "reflect"
 
    gomock "github.com/golang/mock/gomock"
 )
 
 // MockDB is a mock of DB interface.
 type MockDB struct {
    ctrl     *gomock.Controller
    recorder *MockDBMockRecorder
 }
 
 // MockDBMockRecorder is the mock recorder for MockDB.
 type MockDBMockRecorder struct {
    mock *MockDB
 }
 
 // NewMockDB creates a new mock instance.
 func NewMockDB(ctrl *gomock.Controller) *MockDB {
    mock := &MockDB{ctrl: ctrl}
    mock.recorder = &MockDBMockRecorder{mock}
    return mock
 }
 
 // EXPECT returns an object that allows the caller to indicate expected use.
 func (m *MockDB) EXPECT() *MockDBMockRecorder {
    return m.recorder
 }
 
 // Get mocks base method.
 func (m *MockDB) Get(key string) (int, error) {
    m.ctrl.T.Helper()
    ret := m.ctrl.Call(m, "Get", key)
    ret0, _ := ret[0].(int)
    ret1, _ := ret[1].(error)
    return ret0, ret1
 }
 
 // Get indicates an expected call of Get.
 func (mr *MockDBMockRecorder) Get(key interface{}) *gomock.Call {
    mr.mock.ctrl.T.Helper()
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), key)
 }
 复制代码

在测试的使用mock对象

func TestGetFromDB(t *testing.T) {
   ctrl := gomock.NewController(t)
   defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用
 
   m := NewMockDB(ctrl)
   m.EXPECT().Get(gomock.Eq("Tom")).Return(100, errors.New("not exist")) //设置期望返回结果,可以设置可调用次数times/AnyTimes
 
   if v := GetFromDB(m, "Tom"); v != -1 {
     t.Fatal("expected -1, but got", v)
   }
 }
 复制代码

goMock支持对特定输入打桩和对任意输入打桩(gomock.any()),可根据具体情况使用;

实际项目中,可以用gomock来mock dao层和rpc层代码,隔离外部依赖文章来源地址https://www.toymoban.com/news/detail-491542.html

到了这里,关于Go单元测试及框架使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • C#-单元测试NUnit框架的安装及使用

    目录 一、NUnit的安装(Visual Studio) 二、NUnit常见Attribute的使用 三、NUnit的断言系统 通过VS菜单栏中的NuGet包管理器搜索并安装 1、若需要使用NUnit框架自带的runner运行,则在NuGet包管理器中搜索NUnit.Console安装, 安装后,在packagesNUnit.ConsoleRunner.3.X.Xtools中找到unit3-console并运行;

    2024年04月14日
    浏览(10)
  • Mybatis 的多种标签使用以及 Spring 框架单元测试

    Mybatis 的多种标签使用以及 Spring 框架单元测试

    在讲解 Mybatis 的标签之前, 要先介绍一下另一个 Java 的好帮手 Spring 框架内置的 JUnit 测试框架. 为什么要在 Mybatis 学习之前了解 JUnit 呢 ? 很大一部分原因不仅仅是因为单元测试是写完项目后开发人员自己需要做的, 更重要的是当前阶段学习中, 利用 JUnit 可以更简单的构造数据来

    2024年02月09日
    浏览(9)
  • SpringBoot单元测试--Mockito+Junit5框架使用

    SpringBoot单元测试--Mockito+Junit5框架使用

    作为程序员为了提前发现代码bug,优化代码; 通常我们写完某个功能模块代码后都需要写单元测试对代码块进行测试(特别是敏捷开发中);Java项目最常用的单元测试框架即为Junit(目前最新版本为Junit5),SpringBoot本身也整合了该框架。在写单元测试时代码块中的调到第三方接口方

    2024年02月02日
    浏览(12)
  • ​基于多种语言,使用Selenium实现自动化的常用单元测试框架

    ​基于多种语言,使用Selenium实现自动化的常用单元测试框架

    Selenium是自动化网络应用程序的首选工具。Selenium支持基于Java、C#、PHP、Ruby、Perl、JavaScript和Python等多种编程语言的各种单元测试框架。这些框架用于在 Windows、MacOS 和 Linux 等不同平台的网络应用程序上执行测试脚本。任何成功的自动化流程都有赖于强大的测试框架,这些框架

    2024年01月21日
    浏览(15)
  • C语言单元测试框架——CUnit 安装(windows和Linux)及使用

    C语言单元测试框架——CUnit 安装(windows和Linux)及使用

    昨天把软件测试基础基本上看完了,因为我最近工作问题,需要着重研究下Cunit这个单元测试框架,上午把之前学的基础整理出来了。 刚刚研究了下Cunit除了基本 (Basic)接口,还有三个接口没使用过,我也会经量都尝试下。 下个星期我的任务是写一个墨水屏的驱动,测试的学

    2024年02月09日
    浏览(11)
  • 快速上手 Jest 单元测试框架:使用 Jest Preset 加速配置,灵活应对项目需求

    Jest preset 参数的作用是提供一个预先定义好的配置集合,以便于快速开始使用 Jest 单元测试框架。你可以选择现有的 preset,如 jest-preset-angular 、 create-react-app 或自定义 preset,以根据你的项目需求自动应用一系列配置。 使用示例: 先安装一个现有的 preset,例如 jest-preset-angul

    2024年02月06日
    浏览(14)
  • Go单元测试与集成测试:编写可靠的Go测试用例

    Go语言是一种现代编程语言,它具有简洁的语法、强大的性能和易于使用的并发特性。随着Go语言的发展和广泛应用,编写高质量的测试用例变得越来越重要。在Go语言中,我们可以使用内置的testing包来编写单元测试和集成测试。本文将涵盖Go单元测试与集成测试的核心概念、

    2024年02月19日
    浏览(11)
  • Go语言测试——【单元测试 | Mock测试 | 基准测试】

    Go语言测试——【单元测试 | Mock测试 | 基准测试】

    作者 :非妃是公主 专栏 :《Golang》 博客主页 :https://blog.csdn.net/myf_666 个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩 软件测试 :软件测试(英语:Software Testing),描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。换句话说,软件测

    2024年02月10日
    浏览(10)
  • Go 单元测试之mock接口测试

    Go 单元测试之mock接口测试

    目录 一、gomock 工具介绍 二、安装 三、使用 3.1 指定三个参数 3.2 使用命令为接口生成 mock 实现 3.3 使用make 命令封装处理mock 四、接口单元测试步骤 三、小黄书Service层单元测试 四、flags 五、打桩(stub) 参数 六、总结 6.1 测试用例定义 6.2 设计测试用例 6.3 执行测试用例代码

    2024年04月22日
    浏览(15)
  • Go 单元测试基本介绍

    Go 单元测试基本介绍

    目录 一、单元测试基本介绍 1.1 什么是单元测试? 1.2 如何写好单元测试 1.3 单元测试的优点 1.4 单元测试的设计原则 二、Go语言测试 2.1 Go单元测试概要 2.2 Go单元测试基本规范 2.3 一个简单例子 2.3.1 使用Goland 生成测试文件 2.3.2 运行单元测试 2.3.3 完善测试用例 2.3.5 回归测试

    2024年04月16日
    浏览(16)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包