Golang单元测试与断言编写流程详解

目录
  • 编写单元测试
  • 批量测试(test tables)
  • 执行测试
  • 性能测试
  • 配置计算时间
  • 断言(assertion)

Go 在testing包中内置测试命令go test,提供了最小化但完整的测试体验。标准工具链还包括基准测试和基于代码覆盖的语句,类似于NCover(.NET)或Istanbul(Node.js)。本文详细讲解go编写单元测试的过程,包括性能测试及测试工具的使用,另外还介绍第三方断言库的使用。

编写单元测试

go中单元测试与语言中其他特性一样具有独特见解,如格式化、命名规范。语法有意避免使用断言,并将检查值和行为的责任留给开发人员。

下面通过示例进行说明。我们编写Sum函数,实现数据求和功能:

package main
func Sum(x int, y int) int {
    return x + y
}
func main() {
    Sum(5, 5)
}

然后在单独的文件中编写测试代码,测试文件可以在相同包中,或不同包中。测试代码如下:

package main
import "testing"
func TestSum(t *testing.T) {
    total := Sum(5, 5)
    if total != 10 {
       t.Errorf("Sum was incorrect, got: %d, want: %d.", total, 10)
    }
}

Golang测试功能特性:

  • 仅需要一个参数,必须是t *testing.T
  • 以Test开头,接着单词或词组,采用骆驼命名法,举例:TestValidateClient
  • 调用t.Errort.Fail 表明失败(当然也可以使用t.Errorf提供更多细节)
  • t.Log用于提供非失败的debug信息输出
  • 测试文件必须命名为something_test.go ,举例: addition_test.go

批量测试(test tables)

test tables概念是一组(slice数组)测试输入、输出值:

func TestSum(t *testing.T) {
	tables := []struct {
		x int
		y int
		n int
	}{
		{1, 1, 2},
		{1, 2, 3},
		{2, 2, 4},
		{5, 2, 7},
	}
	for _, table := range tables {
		total := Sum(table.x, table.y)
		if total != table.n {
			t.Errorf("Sum of (%d+%d) was incorrect, got: %d, want: %d.", table.x, table.y, total, table.n)
		}
	}
}

如果需要触发错误,我们可以修改测试数据,或修改代码。这里修改代码return x*y, 输出如下:

=== RUN   TestSum
    math_test.go:61: Sum of (1+1) was incorrect, got: 1, want: 2.
    math_test.go:61: Sum of (1+2) was incorrect, got: 2, want: 3.
    math_test.go:61: Sum of (5+2) was incorrect, got: 10, want: 7.
--- FAIL: TestSum (0.00s)

FAIL

单元测试不仅要正向测试,更要进行负向测试。

执行测试

执行测试有两种方法:

在相同目录下运行命令:

go test

这会匹配任何packagename_test.go的任何文件。

使用完整的包名

go test

现在我们可以运行单元测试了,还可以增加参数go test -v获得更多输出结果。

单元测试和集成测试的区别在于单元测试通常不依赖网络、磁盘等,仅测试一个功能,如函数。

另外还可以查看测试语句覆盖率,增加-cover选项。但高覆盖率未必总是比低覆盖率好,关键是功能正确。

如果执行下面命令,可以生成html文件,以可视化方式查看覆盖率:

go test -cover -coverprofile=c.out
go tool cover -html=c.out -o coverage.html

性能测试

benchmark 测试衡量程序性能,可以比较不同实现差异,理解影响性能原因。

go性能测试也有一定规范:

性能测试函数名必须以Benchmark开头,之后大写字母或下划线。因此BenchmarkFunctionName()Benchmark_functionName()都是合法的,但Benchmarkfunctionname()不合法。这与单元测试以Test开头规则一致。

虽然可以把单元测试和性能测试代码放在相同文件,但尽量避免,文件命名仍然以_test.go结尾。如单元测试文件为simple_test.go,性能测试为benchmark_test.go。

下面通过示例进行说明,首先定义函数:

func IsPalindrome(s string) bool {
	for i := range s {
		if s[i] != s[len(s)-1-i] {
			return false
		}
	}
	return true
}

先编写单元测试,分别编写正向测试和负向测试:

func TestPalindrome(t *testing.T) {
	if !IsPalindrome("detartrated") {
		t.Error(`IsPalindrome("detartrated") = false`)
	}
	if !IsPalindrome("kayak") {
		t.Error(`IsPalindrome("kayak") = false`)
	}
}
func TestNonPalindrome(t *testing.T) {
	if IsPalindrome("palindrome") {
		t.Error(`IsPalindrome("palindrome") = true`)
	}
}

接着编写基准测试(性能测试):

func BenchmarkIsPalindrome(b *testing.B) {
	for i := 0; i < b.N; i++ {
		IsPalindrome("A man, a plan, a canal: Panama")
	}
}

执行性能测试

go test -bench . -run notest

-bench参数执行所有性能测试,也可以使用正则代替. ,默认情况单元测试也会执行,因为单元测试种有错误,可以通过-run 参数指定值不匹配任何测试函数名称,从而仅执行性能测试。

我们还可以指定其他参数,下面示例指定count为2,表示对现有测试执行两次分析。设置GOMAXPROCS为4,查看测试的内存情况,执行这些请求时间为2秒,而不是默认的1秒执行时间。命令如下:

$ go test -bench=. -benchtime 2s -count 2 -benchmem -cpu 4 -run notest
goos: windows
goarch: amd64
pkg: gin01/math
cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
BenchmarkIsPalindrome
BenchmarkIsPalindrome-4         1000000000               1.349 ns/op           0 B/op          0 allocs/op
BenchmarkIsPalindrome-4         1000000000               1.356 ns/op           0 B/op          0 allocs/op
PASS
ok      gin01/math      3.234s

  • -4 : 执行测试的GOMAXPROCS数量
  • 1000000000 :为收集必要数据而运行的次数
  • 1.349 ns/op :测试每个循环执行速度
  • PASS:指示基准测试运行的结束状态。

配置计算时间

定义函数:

func sortAndTotal(vals []int) (sorted []int, total int) {
	sorted = make([]int, len(vals))
	copy(sorted, vals)
	sort.Ints(sorted)
	for _, val := range sorted {
		total += val
		total++
	}
	return
}

对应单元测试如下:

func BenchmarkSort(b *testing.B) {
	rand.Seed(time.Now().UnixNano())
	size := 250
	data := make([]int, size)
	for i := 0; i < b.N; i++ {
		for j := 0; j < size; j++ {
			data[j] = rand.Int()
		}
		sortAndTotal(data)
	}
}

每次执行前,随机生成数组,造成性能测试不准确。

为了更准确计算时间,可以使用下面函数进行控制:

-StopTimer() : 停止计时器方法.

-StartTimer() : 启动计时器方法.

-ResetTimer() : 重置计时器方法.

最终性能测试函数如下:

func BenchmarkSort(b *testing.B) {
	rand.Seed(time.Now().UnixNano())
	size := 250
	data := make([]int, size)
    // 开始前先重置
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
        // 准备数据时停止计时
		b.StopTimer()
		for j := 0; j < size; j++ {
			data[j] = rand.Int()
		}
        // 调用函数时启动计时
		b.StartTimer()
		sortAndTotal(data)
	}
}

断言(assertion)

go测试没有提供断言,对于java开发人员来说有点不习惯。这里介绍第三方库 github.com/stretchr/testify/assert.它提供了一组易理解的测试工具。

assert示例

assert子库提供了便捷的断言函数,可以大大简化测试代码的编写。总的来说,它将之前需要判断 + 信息输出的模式:

import (
  "testing"
  "github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
  var a string = "Hello"
  var b string = "Hello"
  assert.Equal(t, a, b, "The two words should be the same.")
}

观察到上面的断言都是以TestingT为第一个参数,需要大量使用时比较麻烦。testify提供了一种方便的方式。先以testing.T创建一个Assertions对象,Assertions定义了前面所有的断言方法,只是不需要再传入TestingT参数了。

func TestEqual(t *testing.T) {
  assertions := assert.New(t)
  assertion.Equal(a, b, "")
  // ...
}

TestingT类型定义如下,就是对*testing.T做了一个简单的包装:

// TestingT is an interface wrapper around *testing.T
type TestingT interface {
	Errorf(format string, args ...interface{})
}

下面引用官网的一个示例。

首先定义功能函数Addition:

func Addition(a, b int) int {
	return a + b
}

测试代码:

import (
	"github.com/stretchr/testify/assert"
	"testing"
)
// 定义比较函数类型,方便后面批量准备测试数据
type ComparisonAssertionFunc func(assert.TestingT, interface{}, interface{}, ...interface{}) bool
// 测试参数类型
type args struct {
	x int
	y int
}
func TestAddition(t *testing.T) {
	tests := []struct {
		name      string
		args      args
		expect    int
		assertion ComparisonAssertionFunc
	}{
		{"2+2=4", args{2, 2}, 4, assert.Equal},
		{"2+2!=5", args{2, 2}, 5, assert.NotEqual},
		{"2+3==5", args{2, 3}, 5, assert.Exactly},
	}
	for _, tt := range tests {
        // 动态执行断言函数
		t.Run(tt.name, func(t *testing.T) {
			tt.assertion(t, tt.expect, Addition(tt.args.x, tt.args.y))
		})
	}
	assert.Equal(t, 2, Addition(1, 1), "sum result is equal")
}

到此这篇关于Golang单元测试与断言编写流程详解的文章就介绍到这了,更多相关Go单元测试内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • GOLang单元测试用法详解

    目录 概念 go test基本用法 go test 基础用例 测试可执行程序 外部测试包解决循环依赖 测试覆盖比例 测试基准函数 概念 单元测试 UT测试,针对程序来进行正确检测测试工作,一个优秀强壮代码 需要有完美的 UT测试用例 go test基本用法 go test 测试用例放在 *_test.go 文件中,与被测函数放到同一个目录下面go build 时候不会构建成包一部分 被测试用例函数命名 TestXXX. 第一个字母必须大写 测试函数: 检测逻辑是否正确 基准函数以BenChmar

  • Golang单元测试与覆盖率的实例讲解

    1 概述 C/C++和Java(以及大多数的主流编程语言)都有自己成熟的单元测试框架,前者如Check,后者如JUnit,但这些编程框架本质上仍是第三方产品,为了执行单元测试,我们不得不从头开始搭建测试工程,并且需要依赖于第三方工具才能生成单元测试的覆盖率. 相比之下,Go语言官方则提供了语言级的单元测试支持,即testing包,而且仅通过go工具本身就可以方便地生成覆盖率数据,也就是说,单元测试是Go语言的自带属性,除了好好设计自己的单元测试用例外,开发者不需要操心工程搭建的任何细节.没错,G

  • 深入理解Golang的单元测试和性能测试

    前言 大家做开发的应该都知道,在开发程序中很重要的一点是测试,我们如何保证代码的质量,如何保证每个函数是可运行,运行结果是正确的,又如何保证写出来的代码性能是好的,我们知道单元测试的重点在于发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让线上的程序能够在高并发的情况下还能保持稳定.本小节将带着这一连串的问题来讲解Go语言中如何来实现单元测试和性能测试. go语言中自带有一个轻量级的测试框架testing和自带的go test命令来

  • golang 对私有函数进行单元测试的实例

    在待测试的私有函数所在的包内,新建一个xx_test.go文件 书写方式如下: import ( "github.com/stretchr/testify/assert" "testing" ) var XXFunc = yourPrivateFunc func TestXXFunc(t *testing.T) { ret, ... := XXFunc(...) assert.Equal(t, ret, ...) } 就可以了~ 补充:golang test使用(简

  • Golang 单元测试和基准测试实例详解

    目录 前言 Go 单元测试 单元测试覆盖率 基准测试 前言 多人协作的项目里,要保证代码的质量,自然离不开单元测试.开发完一个功能后肯定要对所写的代码进行测试,测试没有问题之后再合并到代码库供他人使用.如果强行合并到代码库可能会影响其他人开发,被上线的话肯定也会导致线上 Bug ,影响用户使用. 所以,单元测试也是一个很重要的事情.单元测试是指在开发中,对一个函数或模块的测试.其强调的是对单元进行测试. Go 单元测试 Go 语言提供了单元测试的框架,只要遵循其规则即可: 测试文件命名: 单元测

  • Golang单元测试与断言编写流程详解

    目录 编写单元测试 批量测试(test tables) 执行测试 性能测试 配置计算时间 断言(assertion) Go 在testing包中内置测试命令go test,提供了最小化但完整的测试体验.标准工具链还包括基准测试和基于代码覆盖的语句,类似于NCover(.NET)或Istanbul(Node.js).本文详细讲解go编写单元测试的过程,包括性能测试及测试工具的使用,另外还介绍第三方断言库的使用. 编写单元测试 go中单元测试与语言中其他特性一样具有独特见解,如格式化.命名规范.语法

  • SpringBoot整合Dozer映射框架流程详解

    目录 1. Dozer 介绍 2. 为什么要使用映射框架 Dozer 3. Dozer 映射框架的使用 1. Dozer 介绍 Dozer 是一个 Java Bean 到 Java Bean 的映射器,它递归地将数据从一个对象复制到另一个对象.Dozer 是用来对两个对象之间属性转换的工具,有了这个工具之后,我们将一个对象的所有属性值转给另一个对象时,就不需要再去写重复的调用 set 和 get 方法. 最重要的是,Dozer 可以确保来自数据库的内部域对象不会渗入外部表示层或外部消费者,它还可

  • Golang 中反射的应用实例详解

    目录 引言 Golang类型设计原则 Golang 中为什么要使用反射/什么场景可以(应该)使用反射 举例场景: 反射的基本用法 反射的性能分析与优缺点 测试反射结构体初始化 测试结构体字段读取/赋值 测试结构体方法调用 优缺点 反射在 okr 中的简单应用 结论 引言 首先来一段简单的代码逻辑热身,下面的代码大家觉得应该会打印什么呢? type OKR struct { id int content string } func getOkrDetail(ctx context.Context,

  • Go单元测试利器testify使用示例详解

    目录 testify assert 包 require 包 mock 包 suite 包 testify 在团队里推行单元测试的时候,有一个反对的意见是:写单元测试耗时太多.且不论这个意见对错,单元测试确实不应该太费时间.这时候,一个好的单测辅助工具,显得格外重要.本文推荐的 testify(github.com/stretchr/te…) 包,具有断言.mock 等功能,能配合标准库,使你的单元测试更加简洁易读. testify 有三个主要功能: 断言,在 assert 包和 require

  • MVC+DAO设计模式下的设计流程详解

    DAO设计 : DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置. 在该层主要完成对象-关系映射的建立,通过这个映射,再通过访问业务对象即可实现对数据库的访问,使得开发中不必再用SQL语句编写复杂的

  • Java代码生成器的制作流程详解

    1. 前言 前几天写了篇关于Mybatis Plus代码生成器的文章,不少同学私下问我这个代码生成器是如何运作的,为什么要用到一些模板引擎,所以今天来说明下代码生成器的流程. 2. 代码生成器的使用场景 我们在编码中存在很多样板代码,格式较为固定,结构随着项目的迭代也比较稳定,而且数量巨大,这种代码写多了也没有什么技术含量,在这种情况下代码生成器可以有效提高我们的效率,其它情况并不适于使用代码生成器. 3. 代码生成器的制作流程 首先我们要制作模板,把样板代码的固定格式抽出来.然后把动态属性绑定

  • 基于gin的golang web开发:路由示例详解

    Gin是一个用Golang编写的HTTP网络框架.它的特点是类似于Martini的API,性能更好.在golang web开发领域是一个非常热门的web框架. 启动一个Gin web服务器 使用下面的命令安装Gin go get -u github.com/gin-gonic/gin 在代码里添加依赖 import "github.com/gin-gonic/gin" 快速启动一个Gin服务器的代码如下 package main import "github.com/gin-

  • golang类型转换组件Cast的使用详解

    开源地址 https://github.com/spf13/cast Cast是什么? Cast是一个库,以一致和简单的方式在不同的go类型之间转换. Cast提供了简单的函数,可以轻松地将数字转换为字符串,将接口转换为bool类型等等.当一个明显的转换是可能的时,Cast会智能地执行这一操作.它不会试图猜测你的意思,例如,你只能将一个字符串转换为int的字符串表示形式,例如"8".Cast是为Hugo开发的,Hugo是一个使用YAML.TOML或JSON作为元数据的网站引擎. 为什么

  • 在IDEA中maven配置MyBatis的流程详解

    一.MyBatis简介 1)MyBatis 是一款优秀的持久层框架 2)MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程 3)MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 [Plain Old Java Objects,普通的 Java对象]映射成数据库中的记录. 如果想了解maven请转到我的上一篇文章中: 二.MyBatis获取 1)在这个网址下获取MyBatis:https://mvnrepositor

  • SpringMvc框架的简介与执行流程详解

    目录 一.SpringMvc框架简介 1.Mvc设计理念 2.SpringMvc简介 二.SpringMvc执行流程 1.流程图解 2.步骤描述 3.核心组件 三.整合Spring框架配置 1.spring-mvc配置 2.Web.xml配置 3.测试接口 4.常用注解说明 四.常见参数映射 1.普通映射 2.指定参数名 3.数组参数 4.Map参数 5.包装参数 6.Rest风格参数 五.源代码地址 一.SpringMvc框架简介 1.Mvc设计理念 M:代表模型Model 模型就是数据,应用

随机推荐