IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Go语言学习笔记之 单元测试 -> 正文阅读

[开发测试]Go语言学习笔记之 单元测试

作者:recommend-item-box type_blog clearfix

Go语言学习笔记之 单元测试

作为一名合格的开发者,不应该在程序开发完之后才开始写测试代码。使用 Go 语言的测试框架,可以在开发的过程中就进行单元测试基准测试

和 go build 命令类似,go test 命 令可以用来执行写好的测试代码,需要做的就是遵守一些规则来写测试。而且,可以将测试无缝 地集成到代码工程和持续集成系统里。

1.单元测试

单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。测试的目的是确认目标 代码在给定的场景下,有没有按照期望工作。一个场景是正向路经测试,就是在正常执行的情况 下,保证代码不产生错误的测试。这种测试可以用来确认代码可以成功地向数据库中插入一条工作记录。

另外一些单元测试可能会测试负向路径的场景,保证代码不仅会产生错误,而且是预期的错误。这种场景下的测试可能是对数据库进行查询时没有找到任何结果,或者对数据库做了无效的更新。在这两种情况下,测试都要验证确实产生了错误,且产生的是预期的错误。总之,不管如何调用或者执行代码,所写的代码行为都是可预期的。

在 Go 语言里有几种方法写单元测试。基础测试(basic test)只使用一组参数和结果来测试 一段代码。 **表组测试(table test)也会测试一段代码,但是会使用多组参数和结果进行测试。**也 可以使用一些方法来模仿(mock)测试代码需要使用到的外部资源,如数据库或者网络服务器。 这有助于让测试在没有所需的外部资源可用的时候,模拟这些资源的行为使测试正常进行。最后,在构建自己的网络服务时,有几种方法可以在不运行服务的情况下,调用服务的功能进行测试。

1.1 基础单元测试

// 这个示例程序展示如何写基础单元测试
package main

import (
	"net/http"
	"testing"
)

const checkMark = "\u2713"// 对号(√)
const ballotX = "\u2717"// 叉号(×)

// TestDownload 确认 http 包的 Get 函数可以下载内容
func TestDownload(t *testing.T) {
	url := "http://www.baidu.com"
	statusCode := 200
	t.Log("")

	t.Log("Given the need to test downloading content.")
	{
		t.Logf("\tWhen checking \"%s\" for status code \"%d\"",
			url, statusCode)
		{
			resp, err := http.Get(url)
			if err != nil {
				t.Fatal("\t\tShould be able to make the Get call.", ballotX, err)
			}
			t.Log("\t\tShould be able to make the Get call.", checkMark)

			defer resp.Body.Close()

			if resp.StatusCode == statusCode {
				t.Logf("\t\tShould receive a \"%d\" status. %v",
					statusCode, checkMark)
			} else {
				t.Errorf("\t\tShould receive a \"%d\" status. %v %v",
					statusCode, ballotX, resp.StatusCode)
			}
		}
	}
}

结果

$ go test -v
=== RUN   TestDownload
    listing01_test.go:17: Given the need to test downloading content.
    listing01_test.go:19:       When checking "http://www.baidu.com" for status code "200"
    listing01_test.go:26:               Should be able to make the Get call. ?
    listing01_test.go:31:               Should receive a "200" status. ?
--- PASS: TestDownload (0.15s)
PASS
ok      grpcProject     0.393s

代码说明

一个测试函数 必须是公开的函数,并且以 Test 单词开头。不但函数名字要以 Test 开头,而且函数的签名必 须接收一个指向 testing.T 类型的指针,并且不返回任何值。如果没有遵守这些约定,测试框 架就不会认为这个函数是一个测试函数,也不会让测试工具去执行它。

指向testing.T类型的指针很重要。这个指针提供的机制可以报告每个测试的输出和状态。测试的输出格式没有标准要求。

使用方法 t.Log 来输出测试 的消息。这个方法还有一个名为 t.Logf 的版本,可以格式化消息**。如果执行 go test 的时候 没有加入冗余选项(-v),除非测试失败,否则我们是看不到任何测试输出的。**

每个测试函数都应该通过解释这个测试的给定要求(given need),来说明为什么应该存在这 个测试。对这个例子来说,给定要求是测试能否成功下载数据。在声明了测试的给定要求后,测 试应该说明被测试的代码应该在什么情况下被执行,以及如何执行。

测试的输出很清 晰,能描述测试的目的,同时包含了足够的信息。我们知道具体是哪个单元测试被运行,测试通 过了,并且运行消耗的时间是 393毫秒。

1.2 表组测试

如果测试可以接受一组不同的输入并产生不同的输出的代码,那么应该使用表组测试的方法进行测试。

表组测试除了会有一组不同的输入值和期望结果之外,其余部分都很像基础单元测试。 测试会依次迭代不同的值,来运行要测试的代码。每次迭代的时候,都会检测返回的结果。这便于在一个函数里测试不同的输入值和条件。

这里还是介绍一下,goland自带的表组测试例子。

package exmaple

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

1.选中方法,点击鼠标右键,点击Generate(直接Alt+Insert也行)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MNhgeGaK-1630312080357)(C:\Users\penggewu\AppData\Roaming\Typora\typora-user-images\image-20210830155453077.png)]

2.选择Test for selection

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5HVG2OL-1630312080359)(C:\Users\penggewu\AppData\Roaming\Typora\typora-user-images\image-20210830155545851.png)]

Test for selection:为该函数生成测试。

Test for file:为该文件所有函数生成测试。

Test for package:为该包下所有函数生成测试。

3.在生成的单元测试函数中添加测试用例

package exmaple

import "testing"

func TestAddNum(t *testing.T) {
	type args struct {
		a int
		b int
	}
	tests := []struct {
		name string
		args args
		want int
	}{
		// TODO: Add test cases.
		{
			name: "1 add 1 should be 2",
			args: args{
				a: 1,
				b: 1,
			},
			want: 2,
		},
		{
			name: "3 add 4 should be 7",
			args: args{
				a: 3,
				b: 4,
			},
			want: 7,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := AddNum(tt.args.a, tt.args.b); got != tt.want {
				t.Errorf("AddNum() = %v, want %v", got, tt.want)
			}
		})
	}
}

结果:

$ go test -v
=== RUN   TestAddNum
=== RUN   TestAddNum/1_add_1_should_be_2
=== RUN   TestAddNum/3_add_4_should_be_7
--- PASS: TestAddNum (0.00s)
    --- PASS: TestAddNum/1_add_1_should_be_2 (0.00s)
    --- PASS: TestAddNum/3_add_4_should_be_7 (0.00s)
PASS
ok      grpcProject/exmaple     0.228s

推荐用这种生成方式,手敲代码容易出错。

1.3 基准测试

基准测试(一种压力测试)是一种测试代码性能的方法。

想要测试解决同一问题的不同方案的性能,以及查看 哪种解决方案的性能更好时,基准测试就会很有用。基准测试也可以用来识别某段代码的 CPU或者内存效率问题,而这段代码的效率可能会严重影响整个应用程序的性能。许多开发人员会用 基准测试来测试不同的并发模式,或者用基准测试来辅助配置工作池的数量,以保证能最大化系统的吞吐量。 让我们看一组基准测试的函数,找出将整数值转为字符串的最快方法。

package exmaple

import (
	"fmt"
	"testing"
)

// BenchmarkSprintf 对 fmt.Sprintf 函数进行基准测试
func BenchmarkSprintf(b *testing.B) {
	number := 10

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		fmt.Sprintf("%d", number)
	}
}

基准测试函数必须以 Benchmark 开头,接受一个指向 testing.B 类型的指针作为唯一参数。 为了让基准测试框架能准确测试性能,它必须在一段时间内反复运行这段代码,所以这里使用了 for 循环。for 循环展示了如何使用 b.N 的值。

基准测试框架默认会在持续 1 秒的时间内,反复调用需要测试的函数。测试框架每次调用测试函数时,都会增加 b.N 的值。第一次调用时,b.N 的值为 1。需要注意,一定要将所有要进 行基准测试的代码都放到循环里,并且循环要使用 b.N 的值。否则,测试的结果是不可靠的。

如果我们只希望运行基准测试函数,需要加入-bench 选项

go test -v -run="none" -bench="BenchmarkSprintf"

在这次 go test 调用里,我们给-run 选项传递了字符串"none",来保证在运行制订的基 准测试函数之前没有单元测试会被运行。这两个选项都可以接受正则表达式,来决定需要运行哪 些测试。由于例子里没有单元测试函数的名字中有 none,所以使用 none 可以排除所有的单元 测试。

查看一下输出:

$ go test -v -run="none" -bench="BenchmarkSprintf"
goos: windows
goarch: amd64
pkg: grpcProject/exmaple
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSprintf
BenchmarkSprintf-12     18444739                64.70 ns/op
PASS
ok      grpcProject/exmaple     1.503s

这个输出一开始明确了没有单元测试被运行,之后开始运行 BenchmarkSprintf 基准测 试。在输出 PASS 之后,可以看到运行这个基准测试函数的结果。第一个数字 18444739表示在 循环中的代码被执行的次数。在这个例子里,一共执行了 1844万次。之后的数字表示代码的性能, 单位为每次操作消耗的纳秒(ns)数。这个数字展示了这次测试,使用 Sprintf 函数平均每次 花费了 64 纳秒。

最后,运行基准测试输出了 ok,表明基准测试正常结束。之后显示的是被执行的代码文件的名字。 最后,输出运行基准测试总共消耗的时间。默认情况下,基准测试的最小运行时间是 1 秒。你会看到 这个测试框架持续运行了大约 1.5 秒。如果想让运行时间更长,可以使用另一个名为-benchtime 的 选项来更改测试执行的最短时间。让我们再次运行这个测试,这次持续执行 3 秒。

$ go test -v -run="none" -bench="BenchmarkSprintf" -benchtime="3s"
goos: windows
goarch: amd64
pkg: grpcProject/exmaple
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSprintf
BenchmarkSprintf-12     56794875                63.31 ns/op
PASS
ok      grpcProject/exmaple     3.901s

这次 Sprintf 函数运行了 5679万次,持续了 3.901 秒。这个函数的执行性能并没有太大的 变化,这次的性能是每次操作消耗 63 纳秒。有时候,增加基准测试的时间,会得到更加精确的 性能结果。对大多数测试来说,超过 3 秒的基准测试并不会改变测试的精确度。只是每次基准测试的结果会稍有不同。

运行基准测试时,另一个很有用的选项是-benchmem 选项。这个选项可以提供每次操作分 配内存的次数,以及总共分配内存的字节数。

$ go test -v -run="none" -bench="BenchmarkSprintf" -benchtime="3s" -benchmem
goos: windows
goarch: amd64
pkg: grpcProject/exmaple
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSprintf
BenchmarkSprintf-12     56487511                63.43 ns/op            2 B/op          1 allocs/op
PASS
ok      grpcProject/exmaple     3.879s

这次输出的结果会多出两组新的数值:一组数值的单位是 B/op,另一组的单位是 allocs/op。

单位为 allocs/op 的值表示每次操作从堆上分配内存的次数。

单位为 B/op 的值表示每次操作分配的字节数。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-08-31 15:45:12  更:2021-08-31 15:46:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/11 4:45:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码