Go单元测试

Go单元测试框架,遵循规则整理如下:

1、文件命名规则:
含有单元测试代码的go文件必须以_test.go结尾,Go语言测试工具只认符合这个规则的文件
单元测试文件名_test.go前面的部分最好是被测试的方法所在go文件的文件名。 2、函数声明规则:
测试函数的签名必须接收一个指向testing.T类型的指针,并且函数没有返回值。 3、函数命名规则:
单元测试的函数名必须以Test开头,是可导出公开的函数,最好是Test+要测试的方法函数名。

go test常用选项整理如下(下文有用到)

-v	:查看更详细的测试结果输出
-run:指定输出哪个函数的测试结果,默认为当前路径下所有单元测试函数
-coverprofile:指定生成覆盖率测试输出文件

示例代码

fibonacci.go

package unit

func Fibonacci(n int) []int {

	if n <= 0 {
return nil
} f := make([]int, n)
f[0] = 1
a, b := 1, 1 for i := 1; i < n; i++ {
a, b = b, a+b
f[i] = a
} return f
}

fibonacci_test.go

package unit

import (
"fmt"
"testing"
) func TestFibonacci(t *testing.T) {
fmt.Println(Fibonacci(6))
}

代码测试

$ go test -v -run=TestFibonacci
=== RUN TestFibonacci
[1 1 2 3 5 8]
--- PASS: TestFibonacci (0.00s)
PASS
ok learning/testing/unit 0.002s

覆盖率测试

通过控制不同场景、不同参数等因素,来尽可能测试运行到所有代码,以提高测试覆盖率,Go提供可视化、量化工具帮助查看测试覆盖率,测试覆盖率仅供参考。

filetype.go

package unit

import (
"fmt"
"golang.org/x/sys/unix"
) func FileType(mode int) { switch mode {
case mode & unix.S_IFBLK:
fmt.Println("Block File")jieguo
case mode & unix.S_IFCHR:
fmt.Println("Char File")
case mode & unix.S_IFIFO:
fmt.Println("Pipe File")
case mode & unix.S_IFREG:
fmt.Println("Regular File")
case mode & unix.S_IFSOCK:
fmt.Println("Socket File")
case mode & unix.S_IFLNK:
fmt.Println("Link File")
case mode & unix.S_IFDIR:
fmt.Println("Dir File")
default:
fmt.Println("Unknown File")
}
}

filetype_test.go

package unit

import (
"golang.org/x/sys/unix"
"testing"
) func TestFileType(t *testing.T) {
FileType(unix.S_IFBLK)
FileType(unix.S_IFSOCK)
}

使用 go test 选项 -coverprofile 指定生成覆盖率文件

$ go test -v -run=TestFileType -coverprofile=c.out
=== RUN TestFileType
Block File
Socket File
--- PASS: TestFileType (0.00s)
PASS
coverage: 14.3% of statements
ok learning/testing/unit 0.002s

输出显示 coverage: 14.3% of statements,即提示代码测试覆盖率为14.3%

c.out生成测试覆盖率报告,可以通过 go tool cover -html=c.out -o=tag.html,生成一个html格式显示测试覆盖率。使用浏览器打开tag.html显示如下,网页绿色显示为测试已覆盖到,红色显示为测试未覆盖到,可以修改filetype_test.go完善测试程序,将测试覆盖率达到100%。

Go基准测试

基准测试是一种测试代码性能的方法,主要通过测试CPU和内存等因素,来评估代码性能,以此来调优代码性能。

Go基准测试编写规则

1、文件命名规则:
含有单元测试代码的go文件必须以_test.go结尾,Go语言测试工具只认符合这个规则的文件
单元测试文件名_test.go前面的部分最好是被测试的方法所在go文件的文件名。 2、函数声明规则:
测试函数的签名必须接收一个指向testing.B类型的指针,并且函数没有返回值。 3、函数命名规则:
单元测试的函数名必须以Benchmark开头,是可导出公开的函数,最好是Benchmark+要测试的方法函数名。 4、函数体设计规则:
b.N 是基准测试框架提供,用于控制循环次数,循环调用测试代码评估性能。
b.ResetTimer()/b.StartTimer()/b.StopTimer()用于控制计时器,准确控制用于性能测试代码的耗时。

Go基准测试go test命令选项

操作命令:go test -bench=. benchtime=3s -run=none -cpuprofile
-bench :go test默认不会基准测试,需要使用bench启动基准测试,指定匹配基准测试的函数,“.”表示运行所有基准测试 -benchtime :测试时间默认为1s,如果想测试运行时间更长,用-benchtime指定 -run :默认情况下go test会运行单元测试,为防止其干扰基准测试输出结果,
可使用-run过滤单元测试,使用none完全屏蔽,“.”运行所有单元测试 -count :指定执行多少次 $ go test -bench=. -run=none
goos: linux
goarch: amd64
pkg: learning/test/benchmark
BenchmarkSprintf-12 20000000 70.6 ns/op
PASS
ok learning/test/benchmark 1.485s 其中BenchmarkSprintf-12,12表示GOMAXPROCS,20000000表示循环次数,70.6 ns/op表示单次循环操作花费时间 -cpuprofile :生成运行时CPU详细信息
go test -bench=. -run=none -cpuprofile=xxx xxx 将会使用两个生成文件,然后进入交互模式:
$ go tool pprof benchmark.test cpu.out Sprintf.test
File: benchmark.test
Type: cpu
Time: Mar 18, 2019 at 5:01pm (CST)
Duration: 1.70s, Total samples = 1.62s (95.21%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1030ms, 63.58% of 1620ms total
Showing top 10 nodes out of 57
flat flat% sum% cum cum%
220ms 13.58% 13.58% 370ms 22.84% runtime.mallocgc
130ms 8.02% 21.60% 550ms 33.95% fmt.(*pp).doPrintf
120ms 7.41% 29.01% 280ms 17.28% fmt.(*fmt).fmtInteger
120ms 7.41% 36.42% 170ms 10.49% sync.(*Pool).Put
80ms 4.94% 41.36% 110ms 6.79% fmt.(*buffer).Write (inline)
80ms 4.94% 46.30% 80ms 4.94% runtime.memmove
80ms 4.94% 51.23% 140ms 8.64% sync.(*Pool).Get
70ms 4.32% 55.56% 410ms 25.31% fmt.(*pp).printArg
70ms 4.32% 59.88% 90ms 5.56% sync.(*Pool).pin
60ms 3.70% 63.58% 340ms 20.99% fmt.(*pp).fmtInteger
(pprof) quit -benchmem :提供每次操作分配内存的次数,以及每次分配的字节数。
$ go test -bench=. -benchmem -run=none
goos: linux
goarch: amd64
pkg: learning/test/benchmark
BenchmarkSprintf-12 20000000 70.5 ns/op 16 B/op 2 allocs/op
BenchmarkFormat-12 20000000 77.1 ns/op 18 B/op 2 allocs/op
BenchmarkItoa-12 20000000 78.4 ns/op 18 B/op 2 allocs/op
PASS
ok learning/test/benchmark 4.754s

性能对比

下面是两个关于性能测试对比的示例

值传参和指针传参

package benchmark

import (
"testing"
) type Cat struct {
Name string
Color string
} // 值类型传参
func Meow(c Cat) {
c.Color = "white"
} // 指针类型传参
func Miaow(c *Cat) {
c.Color = "black"
} func BenchmarkMeow(b *testing.B) {
c := Cat{"Meow", "yellow"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Meow(c)
}
} func BenchmarkMiaow(b *testing.B) {
c := Cat{"Miaow", "yellow"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Miaow(&c)
}
}

性能测试结果

$ go test -bench=. -run=none -benchmem
goos: linux
goarch: amd64
pkg: learning/testing/benchmark
BenchmarkMeow-12 2000000000 0.47 ns/op 0 B/op 0 allocs/op
BenchmarkMiaow-12 2000000000 0.23 ns/op 0 B/op 0 allocs/op
PASS
ok learning/testing/benchmark 1.477s

可以看出在本示例代码中,不同传参方式,指针传递参数比值传递参数性能要优。因为值传参过程中,创建了一个临时对象(变量) ,然后将数据完整拷贝到临时对象,有时间花销。这种情况也包括函数返回值。当然在实际开发过程中,需优先考虑实际场景,如果二者都可以达到预期效果,再考虑性能优化,选择指针类型传参。

slice的不同使用方式

package benchmark

import (
"testing"
) const TotalTimes = 1000000 func StaticCapacity() { // 提前一次性分配好slice所需内存空间,中间不需要再扩容,len为0,cap为1000000
var s = make([]byte, 0, TotalTimes) for i := 0; i < TotalTimes; i++ {
s = append(s, 0)
//fmt.Printf("len = %d, cap = %d\n", len(s), cap(s))
}
} func DynamicCapacity() { // 依赖slice底层自动扩容,中间会有很多次扩容,每次都从新分配一段新的内存空间,
// 然后把数据拷贝到新的slice内存空间,然后释放旧空间,导致引发不必要的GC
var s []byte for i := 0; i < TotalTimes; i++ {
s = append(s, 0)
//fmt.Printf("len = %d, cap = %d\n", len(s), cap(s))
}
} func BenchmarkStaticCapacity(b *testing.B) {
b.ResetTimer() for i := 0; i < b.N; i++ {
StaticCapacity()
}
} func BenchmarkDynamicCapacity(b *testing.B) {
b.ResetTimer() for i := 0; i < b.N; i++ {
DynamicCapacity()
}
}

性能测试结果

$ go test -bench=. -run=none -benchmem
goos: linux
goarch: amd64
pkg: learning/testing/benchmark
BenchmarkStaticCapacity-12 3000 912668 ns/op 1007617 B/op 1 allocs/op
BenchmarkDynamicCapacity-12 1000 1935269 ns/op 5863427 B/op 35 allocs/op
PASS
ok learning/testing/benchmark 4.914s

可以看出StaticCapacity 性能明显优于DynamicCapacity,所以如果同一个slice被大量循环使用,可提前一次性分配好适量的内存空间

总结:在代码设计过程中,对于性能要求比较高的地方,编写基准测试非常重要,这有助于我们开发出性能更优的代码。不过性能、可用性、复用性等也要有一个相对的取舍,不能为了追求性能而过度优化。

参考:https://www.flysnow.org/2017/05/21/go-in-action-go-benchmark-test.html

Go单元测试与基准测试的更多相关文章

  1. Go语言单元测试与基准测试

    目录 单元测试 概述 go test参数解读 单元测试日志 基准测试 基础测试基本使用 基准测试原理 自定义测试时间 测试内存 控制计时器 Go语言拥有一套单元测试和性能测试系统,仅需要添加很少的代码 ...

  2. Go 单元测试、基准测试、并发基准测试

    一.单元测试 要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时需要让文件必须以_test结尾. 单元测试源码文件可以由多个测试用例组成,每个测试用例函数需要以Test为前缀,例如: fu ...

  3. Go语言基础之单元测试

    不写测试的开发不是好程序员.我个人非常崇尚TDD(Test Driven Development)的,然而可惜的是国内的程序员都不太关注测试这一部分. 这篇文章主要介绍下在Go语言中如何做单元测试和基 ...

  4. GO学习-(20) Go语言基础之单元测试

    Go语言基础之单元测试 不写测试的开发不是好程序员.我个人非常崇尚TDD(Test Driven Development)的,然而可惜的是国内的程序员都不太关注测试这一部分. 这篇文章主要介绍下在Go ...

  5. Go入门指南

    第一部分:学习 Go 语言 第1章:Go 语言的起源,发展与普及 1.1 起源与发展 1.2 语言的主要特性与发展的环境和影响因素 第2章:安装与运行环境 2.1 平台与架构 2.2 Go 环境变量 ...

  6. go 面试题总结

    1.什么是goroutine,他与process, thread有什么区别? 2. 什么是channel,为什么它可以做到线程安全? 3. 了解读写锁吗,原理是什么样的,为什么可以做到? 4. 如何用 ...

  7. 好的框架需要好的 API 设计 —— API 设计的六个原则

    说到框架设计,打心底都会觉得很大很宽泛,而 API 设计是框架设计中的重要组成部分.相比于有很多大佬都认可的面向对象的六大原则.23 种常见的设计模式来说,API 设计确实缺少行业公认的原则或者说设计 ...

  8. Go-项目结构和代码组织

    简介 做大量的输入,通过对比.借鉴,加上自己的经验,产出一个尽可能优的方案. 开源界优秀项目的结构示例 因为最新的 Go 版本已经使用 module 作为版本依赖,所以,所有项目的 vendor 我都 ...

  9. 《Go语言实战》读书笔记

    <Go语言实战>中文版pdf 百度网盘: https://pan.baidu.com/s/1kr-gMzaPAn8BFZG0P24Oiw 提取码: r6rt 书籍源码:https://gi ...

随机推荐

  1. DEDE [field:global name=autoindex/] 按序列号递增

    在用织梦仿站的时候,有些class样式是btn1.btn2这样的样式循环的,这个时候1.2可以用[field:global name=autoindex/ ] 循环得到,[field:global n ...

  2. node搭环境(四)--webpack启服务运行VUE模块文件(手写简单脚手架)

    webpack启服务步骤: 1.新建空文件夹webpack-vue.在空文件夹右键点击- GIt Bath here--输入cnpm init--按程序走完会生成package.json文件 2.打开 ...

  3. VS2013 自定义项目模板以及制作.vsix文件

    一.环境检查 打开VS2013新建项目.如果在"其他项目类型"中不包含扩展性节点,则需要下载并安装vs2013 SDK. 二.创建项目模板 1,在VS中新建一个类库项目(此处仅以类 ...

  4. 第三方缓存软件memcached和redis异同

    memcached和redis相同点:都是以键值对的形式来存储数据,通俗讲就是一个大的hashtable缓存数据都是存在内容中 key-value 不同点:memcached:1.一个key所对应的值 ...

  5. hibernate课程 初探一对多映射1-1 课程简介

    hibernate 常见映射类型 one-to-many many-to-one one-to-one many-to-many

  6. ab (ApacheBench)命令

    ab (ApacheBench)命令 参数 -n 在测试会话中所执行的请求个数.默认时,仅执行一个请求 -c 一次产生的请求个数.默认是一次一个 -t 测试所进行的最大秒数 -k 启用HTTP Kee ...

  7. Gremlin--一种支持对图表操作的语言

    Gremlin 是操作图表的一个非常有用的图灵完备的编程语言.它是一种Java DSL语言,对图表进行查询.分析和操作时使用了大量的XPath. Gremlin可用于创建多关系图表.因为图表.顶点和边 ...

  8. 初学:react-native 轮播图

    参考资料:http://reactscript.com/react-native-card-carousel-component/ import React, {Component} from 're ...

  9. Jmeter入门5 关联 响应数据传递-正则表达式提取器

    在测试过程中,遇到一个问题:用户登录成功后服务器会返回一个登录凭证,之后所有的操作都需要带上此凭证.我们怎么获取登录凭证并传递给后续的操作? Jmeter提供了正则表达式提取器,用变量提取参数,后续通 ...

  10. 数黑格有多少个,模拟题,POJ(1656)

    题目链接:http://poj.org/problem?id=1656 #include <stdio.h> #include <iostream> #include < ...