go test
命令只运行单元测试,添加-bench=.
参数,go test同时执行单元测试和基准测试,当然可以通过正则过滤只运行基准测试,例如-bench=^Benchmark
。添加-conver
参数展示测试覆盖率。
测试代码
测试5种字符串拼接效果,共两个文件splice.go和splice_test.go
splice.go文件内容如下:
package splice
import (
"bytes"
"fmt"
"strings"
)
// SpliceWithPlus 使用+号拼接字符串
func SpliceWithPlus(s1 string, s2 string) string {
return s1 + s2
}
// SpliceWithSprintf 使用fmt.Sprintf拼接字符串
func SpliceWithSprintf(s1 string, s2 string) string {
return fmt.Sprintf("%s%s", s1, s2)
}
// SpliceWithJoin 使用strings.Join拼接字符串
func SpliceWithJoin(s1 string, s2 string) string {
return strings.Join([]string{s1, s2}, "")
}
// SpliceWithBuilder 使用strings.Builder拼接字符串
func SpliceWithBuilder(s1 string, s2 string) string {
var builder strings.Builder
builder.WriteString(s1)
builder.WriteString(s2)
return builder.String()
}
// SpliceWithBuilder 使用bytes.Buffer拼接字符串
func SpliceWithBuffer(s1 string, s2 string) string {
var bf bytes.Buffer
bf.WriteString(s1)
bf.WriteString(s2)
return bf.String()
}
测试文件splice_test.go内容如下:
package test_example
import "testing"
var (
count = 10
s1 = strings.Repeat("1234567890", count)
s2 = strings.Repeat("0987654321", count)
want = s1 + s2
)
func TestSpliceString(t *testing.T) {
if got := SpliceWithPlus(s1, s2); got != want {
t.Errorf("SpliceWithPlus() = %v, want %v", got, want)
}
if got := SpliceWithSprintf(s1, s2); got != want {
t.Errorf("SpliceWithSprintf() = %v, want %v", got, want)
}
if got := SpliceWithJoin(s1, s2); got != want {
t.Errorf("SpliceWithJoin() = %v, want %v", got, want)
}
if got := SpliceWithBuilder(s1, s2); got != want {
t.Errorf("SpliceWithBuilder() = %v, want %v", got, want)
}
if got := SpliceWithBuffer(s1, s2); got != want {
t.Errorf("SpliceWithBuffer() = %v, want %v", got, want)
}
}
func BenchmarkSpliceWithPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
SpliceWithPlus(s1, s2)
}
}
func BenchmarkSpliceWithSprintf(b *testing.B) {
for i := 0; i < b.N; i++ {
SpliceWithSprintf(s1, s2)
}
}
func BenchmarkSpliceWithJoin(b *testing.B) {
for i := 0; i < b.N; i++ {
SpliceWithJoin(s1, s2)
}
}
func BenchmarkSpliceWithBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
SpliceWithBuilder(s1, s2)
}
}
func BenchmarkSpliceWithBuffer(b *testing.B) {
for i := 0; i < b.N; i++ {
SpliceWithBuffer(s1, s2)
}
}
单元测试
单元测试常用参数:
-v
:测试时显示详细信息,示例:go test -v-list
: 列出测试、基准测试函数,示例:go test -list-run
: 指定要运行的测试函数,示例:go test -run TestSpliceString-failfast
: 在第一个失败的测试处停止,退出测试,示例 go test -failfast-count=1
: 禁用缓存,示例:go test -count=1-cpu=4
: 限制cpu数量,把参数值传递给GOMAXPROCS,示例:go test -cpu=4-race
: 检测竞态条件,示例:go test -race
单元测试范围示例:
# 执行当前下所有单元测试,包括子文件夹
go test ./...
# 执行当前目录下所有单元测试,不包括子文件夹
go test ./dir
# 执行指定文件单元测试,指定测试文件和被测试文件
go test splice.go splice_test.go
# 运行指定单元测试用例
go test -run TestSpliceString
基准测试
基准测试框架对一个测试用例的默认测试时间是 1 秒。开始测试时,当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调用基准测试用例函数。
基准测试常用参数:
-bench
: 基准测试必填参数,参数值是正则表达式, -bench=. 表示同时运行单元测试和基准测试; -bench=^Benchmark 表示执行当前目录下所有基准测试,也可以具体到某个函数的基准测试。benchtime=3s
: 自定义测试时间,示例:go test -bench=. -benchtime=3s-benchmem
: 显示内存分配统计信息,示例:go test -bench=. -benchmem-cpu=4
: 限制线程数量,把参数值传递给GOMAXPROCS,示例:go test -bench=. -cpu=4
基准测试范围示例:
# 执行当前目录下所有基准测试,包括子文件夹
go test -bench=. ./...
# 执行当前目录下的基准测试,不包括子文件夹
go test -bench=. ./dir
# 执行指定文件基准测试,指定测试文件和被测试文件
go test -bench=. splice.go splice_test.go
# 运行指定单元基准测试
go test -bench=BenchmarkSpliceWithPlus
修改splice_test.go文件的count变量值,分别为1、10、100,进行基准测试,结果如下:
go version go1.17.2 windows/amd64
# count=1,10bytes 长度字符串拼接基准压测
$ go test -bench=. -benchmem
goos: windows
goarch: amd64
pkg: demo/test_example
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSpliceWithPlus-12 80041087 14.95 ns/op 0 B/op 0 allocs/op
BenchmarkSpliceWithSprintf-12 8744901 128.1 ns/op 56 B/op 3 allocs/op
BenchmarkSpliceWithJoin-12 33571974 35.15 ns/op 24 B/op 1 allocs/op
BenchmarkSpliceWithBuilder-12 20154652 59.78 ns/op 48 B/op 2 allocs/op
BenchmarkSpliceWithBuffer-12 19531503 62.91 ns/op 88 B/op 2 allocs/op
PASS
ok demo/test_example 6.314s
# count=10,100bytes 长度字符串拼接基准压测
$ go test -bench=. -benchmem
goos: windows
goarch: amd64
pkg: demo/test_example
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSpliceWithPlus-12 15102146 66.62 ns/op 208 B/op 1 allocs/op
BenchmarkSpliceWithSprintf-12 6655336 181.0 ns/op 240 B/op 3 allocs/op
BenchmarkSpliceWithJoin-12 17094942 70.57 ns/op 208 B/op 1 allocs/op
BenchmarkSpliceWithBuilder-12 10660695 112.0 ns/op 336 B/op 2 allocs/op
BenchmarkSpliceWithBuffer-12 5528898 201.4 ns/op 640 B/op 3 allocs/op
PASS
ok demo/test_example 6.610s
# count=100,1000bytes 长度字符串拼接基准压测
$ go test -bench=. -benchmem
goos: windows
goarch: amd64
pkg: demo/test_example
cpu: Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
BenchmarkSpliceWithPlus-12 2898604 387.1 ns/op 2048 B/op 1 allocs/op
BenchmarkSpliceWithSprintf-12 2218687 511.3 ns/op 2081 B/op 3 allocs/op
BenchmarkSpliceWithJoin-12 2922162 412.0 ns/op 2048 B/op 1 allocs/op
BenchmarkSpliceWithBuilder-12 2015064 607.7 ns/op 3072 B/op 2 allocs/op
BenchmarkSpliceWithBuffer-12 999949 1186 ns/op 6144 B/op 3 allocs/op
PASS
ok demo/test_example 8.067s
基准压测函数后面数字12,表示执行基准测试使用的线程数量,可以通过-cpu=4
参数来修改。
5种字符串拼接性能最好的是BenchmarkSpliceWithPlus,其次是BenchmarkSpliceWithJoin。
Benchstat工具
单纯只压测一次结果不大准确,因此使用工具Benchstat 计算和比较有关基准的统计数据。
安装:
go install golang.org/x/perf/cmd/benchstat@latest
用法:
benchstat [-delta-test name] [-geomean] [-html] [-sort order] old.txt [new.txt] [more.txt …]
示例:
# 第一次压测某个函数
go test -run=NONE -bench=BenchmarkSpliceWithSprintf -count=5 | tee -a old.txt
# 优化后(使用+号替换fmt.Sprintf拼接字符串)第二次压测某个函数
go test -run=NONE -bench=BenchmarkSpliceWithSprintf -count=5 | tee -a new.txt
# 查看压测结果
benchstat old.txt
benchstat new.txt
# 比较两次的统计结果
benchstat old.txt new.txt
# name old time/op new time/op delta
# SpliceWithSprintf-12 128ns ± 1% 33ns ± 0% -74.63% (p=0.000 n=14+5)
任何大于 5% 的值都表明某些样本不可靠,在这种情况下,您应该重新运行基准测试,尽可能保持环境稳定以提高可靠性。
测试覆盖率
go test命令还支持展示测试覆盖率信息。
测试覆盖率常用参数:
-cover
: 测试覆盖率必填参数,示例:go test -v -cover-coverprofile
: 导出单元测试覆盖率统计信息到文件,示例:go test -coverprofile=cover.out-html
: 在浏览器查看覆盖哪些代码,示例:go tool cover -html=cover.out
测试覆盖率范围示例:
# 执行当前下所有单元测试,包括子文件夹,并展示覆盖率
go test -cover ./...
# 执行当前目录下所有单元测试,不包括子文件夹,并展示覆盖率
go test -cover ./dir
# 执行指定文件单元测试,指定测试文件和被测试文件,并展示覆盖率
go test -cover splice.go splice_test.go
# 运行指定单元测试用例,并展示覆盖率
go test -cover -run TestSpliceString
# 在浏览器查看覆盖哪些代码
go test -coverprofile=cover.out && go tool cover -html=cover.out
总结
单元测试是项目开发环节必不可少的环节,是检验程序是否符合预期手段,测试代码有可能比实际业务逻辑代码还多,主要原因是测试包括各种测试case,这些case尽可能覆盖率所有业务代码,使用goland IDE写代码,按快捷键Alt+Insert可以自动化添加单元测试代码,只需要填写测试用例,大大减少写测试代码时间。基准测试是性能要求比较高的业务逻辑中必须测试环节,通过暴力压测方式检验程序性能达到什么水平,一般看完成操作耗时和内存分配情况来判断,也是程序性能调优的常用手段。
专题「golang相关」的其它文章 »
- 使用开发框架sponge快速把单体web服务拆分为微服务 (Sep 18, 2023)
- 使用开发框架sponge一天多开发完成一个简单版社区后端服务 (Jul 30, 2023)
- 一个提高开发项目效率的开发框架sponge,支持mysql、mongodb、postgresql、tidb、sqlite (Jan 06, 2023)
- go应用程序性能分析 (Mar 29, 2022)
- channel原理和应用 (Mar 22, 2022)
- go runtime (Mar 14, 2022)
- go调试工具 (Mar 13, 2022)
- cobra (Mar 10, 2022)
- grpc使用实践 (Nov 27, 2020)
- 配置文件viper库 (Nov 22, 2020)
- 根据服务名称查看golang程序的profile信息 (Sep 03, 2019)
- go语言开发规范 (Aug 28, 2019)
- goroutine和channel应用——处理队列 (Sep 06, 2018)
- golang中的context包 (Aug 28, 2018)