Go单元测试及框架使用

news2024/11/25 0:59:53

Go自带测试框架

单元测试

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

简单测试用例定义如下:

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

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

img

img

生成的代码如下:

img

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

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

测试结果:

img

日志打印

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中执行基准测试

img

命令行中执行基准测试

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

img

结果解读

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

img

example测试

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

img

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

模糊测试

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)
		}
	})
}

测试结果

img

第三方框架

总体介绍

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

结论

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

mock框架

golang中常用的stub/mock框架

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

结论

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

其他特定领域mock工具

框架名说明
GoSqlMocksqlmock包,用于单测中mock db操作
miniredis纯go实现的用于单元测试的redis server。它是一个简单易用的、基于内存的redis替代品,它具有真正的TCP接口。当我们为一些包含 Redis 操作的代码编写单元测试时可以使用它来 mock Redis 操作
HttptestGolang官方自带,生成一个模拟的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层代码,隔离外部依赖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

城市NOA转向BEV,头部Tier 1如何笑傲江湖?

主讲&#xff5c;蒋沁宏 编辑&#xff5c;Amy 编者注&#xff1a; 本文是HiEV出品的系列直播「硬核拆解BEV」第三期问答环节内容整理。商汤绝影量产行车智能驾驶研发负责人蒋沁宏&#xff0c;与连线嘉宾寒武纪行歌自动驾驶总监李想、宏景智驾高级工程经理柴可宁、主持嘉宾周琳…

SpringBoot 源码分析初始化应用上下文(1)-createApplicationContext

前言&#xff1a;springBoot的版本是 2.2.4.RELEASE 一、入口 /*** Run the Spring application, creating and refreshing a new* {link ApplicationContext}.* param args the application arguments (usually passed from a Java main method)* return a running {link A…

2023最新版Java 面试突击手册开源(涵盖 p5-p8 技术栈)

前言: 本文收集整理了各大厂常见面试题N道&#xff0c;你想要的这里都有内容涵盖&#xff1a;Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等技术栈&#xff0c;希望大家都能找到适…

【免配置】Qt的mingw使用编译opencv库

【免配置】Qt的mingw_32/64使用编译opencv库 网上在qt中使用mingw编译器配置opencv的时候&#xff0c;通常需要使用cmake编译工具&#xff0c;进行预先编译&#xff0c;步骤比较繁琐&#xff0c;这里推荐一个捷径&#xff0c;直接使用前人编译好的opencv库即可&#xff0c;避免…

软件测试金融项目,在测试的时候一定要避开的一些雷区

软件测试金融项目需要格外谨慎和专注&#xff0c;因为这些项目通常涉及大量的交易、用户隐私和其他敏感信息。以下是一些软件测试金融项目时需要关注的方面&#xff1a; 1. 数据保护 在测试金融项目时&#xff0c;必须确保用户数据和投资信息得到保护。测试人员必须确保测试环…

Flink CDC 实时mysql到mysql

CDC 的全称是 Change Data Capture &#xff0c;在广义的概念上&#xff0c;只要是能捕获数据变更的技术&#xff0c;我们都可以称之为 CDC 。目前通常描述的 CDC 技术主要面向数据库的变更&#xff0c;是一种用于捕获数据库中数据变更的技术。 mysqlcdc需要mysql开启binlog&a…

TCP报文段结构

TCP报文段结构 源端口号和目的端口号&#xff1a;含义从名字就能看出来。 序号和确认号&#xff1a;这二个字段被 TCP 发送方和接收方用来实现可靠数据传输服务&#xff0c;每个字段都是32比特。 接收窗口&#xff1a;该字段用于流量控制&#xff0c;大小为16比特。 首部长度…

VTK学习之vtkProp

vtkProp。渲染场景中数据的可视表达&#xff08;Visible Depictions&#xff09;是由vtkProp的子类负责。 也就是说&#xff0c;数据想要进行可视化显示&#xff0c;需要一个转换过程&#xff0c;这个过程就是转换为vtkProp 这样才能进行渲染展示出来。 而vtkProp子类是vtkA…

一阶电路和二阶电路的时域分析(1)——“电路分析”

小雅兰期末加油冲冲冲&#xff01;&#xff01;&#xff01; 动态电路的方程及其初始条件 动态电路&#xff0c;物理学名词&#xff0c;是指含有储能元件L、C的电路&#xff0c;动态电路方程的阶数通常等于电路中动态元件的个数。 动态电路是指含有储能元件的电路。当动态电路状…

openpnp - 底部相机矫正(subject not found)的原因总结

文章目录 openpnp - 底部相机矫正(subject not found)的原因总结概述问题的由来相机的选择相机焦距的选择相机初始安装距离位置的选择相机安装支柱接触面过大会影响相机模组PCB的安装相机支柱的绝缘问题安装相机模组时的平整度问题相机轴垂直度的问题相机成像时间矫正时的Z轴位…

全球顶尖科学家陈松蹊院士出任百分点数据科学研究院名誉院长

近日&#xff0c;百分点科技正式宣布聘请北京大学数学科学学院、光华管理学院教授&#xff0c;中国科学院院士陈松蹊担任百分点数据科学研究院名誉院长。公司将以此深化布局数据科学领域&#xff0c;助推数字中国建设。 全球顶尖科学家 陈松蹊院士主要从事超高维大数据统计分析…

卷S人的Java岗!全靠这份1000页的面试手册,拿了28K的offer

大家好&#xff0c;最近有不少朋友给鄙人留言&#xff0c;说今年面试实在是太卷了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01;为了帮大家节约时…

JVM内存结构及程序执行的内存分析过程

一. JVM内存结构 1. JVM的内存结构大概分为 堆&#xff08;Heap&#xff09; 线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。 方法区&#xff08;Method Area&#xff09; 线程共享。存储类信息、常量、静态变量、即时编译器编译后的代码。 方…

机器学习-12 卷积神经网络简介

卷积神经网络 引言深度学习发展历程深度应用领域深度学习vs传统机器学习深度神经网络vs浅层神经网络深度学习概述 卷积神经网络CNNBP神经网络CNN概述卷积神经网络大致结构卷积神经网络大致过程 局部连接权值共享非线性映射ReLU&#xff08;Rectified Linear Units&#xff09;池…

盘点一下架构师主流的画图工具(附地址)

盘点一下架构师主流的画图工具&#xff08;附地址&#xff09; 转发我个人微信公众号的内容&#xff0c;后续优先公众号。 一、文章来源 写这篇文章的目的是做个关于常用画图工具的总结。 起源是在架构组时为了降低沟通成本和提高作战效率&#xff0c;我们频繁用图交流&…

13. 精灵动画Sprite和SpriteSequence的基本使用

1. 说明&#xff1a; 在unity二维游戏开发中&#xff0c;有一种精灵类的玩家角色&#xff0c;通过一系列动作的静态图片可以合成该精灵的某一个动作。在QML当中也有一个控件可以实现这种精灵类动画的制作&#xff0c;主要使用到三个控件&#xff1a;Sprite和SpriteSequence和A…

一文解释python中的实例方法,类方法和静态方法作用和区别是啥?该如何使用

我们都知道 &#xff0c;python类中有三种常见的方法 &#xff0c;分别是实例方法 &#xff0c;类方法和静态方法 。那么这几个方法到底有什么作用 &#xff1f; 它们之间有什么区别 &#xff1f;该如何使用 &#xff1f; 带着这些问题 &#xff0c;下面我们就来了解下这三种方…

windows平台python脚本执行环境搭建笔记

1.python脚本环境下载 这里是原始发布源&#xff1a; https://www.python.org/downloads/release/python-3114/https://www.python.org/downloads/release/python-3114/安装时记得添加进系统path&#xff0c;这样你可以随时调用python环境。 2.扩展模块的安装 step1.找到py…

35款优秀的 SpringBoot/SpringCloud 开源项目,开发脚手架,总有一款适合你...

简介 SpringBoot 是一个非常流行的 Java 框架&#xff0c;它可以帮助开发者快速构建应用程序。他不仅继承了 Spring 框架原有的优秀特性&#xff0c;而且还通过简化配置来进一步简化了 Spring 应用的整个搭建和开发过程。 最近&#xff0c;小编蹲点各大开源网站、社区等&#x…

iOS App的打包和上架流程

转载&#xff1a;iOS App的打包和上架流程 - 掘金 1. 创建账号 苹果开发者账号几种开发者账号类型 个人开发者账号 费用&#xff1a;99 美元/年&#xff08;688.00元&#xff09;协作人数&#xff1a;仅限开发者自己不需要填写公司的邓百氏编码&#xff08; D-U-N-S Number…