go代码测试与调优(上)

简介: 在了解golang的测试之前,先了解一下go语言自带的测试工具-go test

go test工具


Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。


go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。


*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。


类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档


运行流程


go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。


使用详解


上次对于go test 并没有详细的阐述,这次补上。


go test 的使用语法如下


go test [build/test flags] [packages] [build/test flags & test binary flags]
# 可以直接 go test 直接运行,那么它将运行本目录下的所有*_test.go的基准测试。
# 还可以进行编译后测试例如 go test build


更多请查看 go help testfunc


go test 命令还会忽略 testdata 目录,该目录用来保存测试需要用到的辅助数据。


go test 有两种运行模式:


1、本地目录模式,在没有包参数(例如 go testgo test -v)调用时发生。在此模式下,go test 编译当前目录中找到的包和测试,然后运行测试二进制文件。在这种模式下,caching 是禁用的。在包测试完成后,go test 打印一个概要行,显示测试状态、包名和运行时间。


2、包列表模式,在使用显示包参数调用 go test 时发生(例如 go test mathgo test ./... 甚至是 go test .)。在此模式下,go 测试编译并测试在命令上列出的每个包。如果一个包测试通过,go test 只打印最终的 ok 总结行。如果一个包测试失败,go test 将输出完整的测试输出。如果使用 -bench-v 标志,则 go test 会输出完整的输出,甚至是通过包测试,以显示所请求的基准测试结果或详细日志记录。


下面详细说明下 go test 的具体用法,flag 的作用及一些相关例子。需要说明的是:一些 flag 支持 go test 命令和编译后的二进制测试文件。它们都能识别加 -test. 前缀的 flag,如 go test -test.v,但编译后的二进制文件必须加前缀 ./sum.test -test.bench=.


参数详解


test flag


以下 flag 可以跟被 go test 命令使用:


  • -args:传递命令行参数,该标志会将 -args 之后的参数作为命令行参数传递,最好作为最后一个标志。


$ go test -args -p=true


  • -c:编译测试二进制文件为 [pkg].test,不运行测试。


$ go test -c && ./sum.test -p=true


  • -exec xprog:使用 xprog 运行测试,行为同 go run 一样,查看 go help run


  • -i:安装与测试相关的包,不运行测试。


$ go test -i


  • -o file:编译测试二进制文件并指定文件,同时运行测试。


go test -o filename


test/binary flag


以下标志同时支持测试二进制文件和 go test 命令。


  • -bench regexp:通过正则表达式执行基准测试,默认不执行基准测试。可以使用 -bench .-bench=.执行所有基准测试。


$ go test -bench=.
  $ go test -c
  $ ./sum.test -test.bench=.


  • -benchtime t:每个基准测试运行足够迭代消耗的时间,time.Duration(如 -benchtime 1h30s),默认 1s。


$ go test -bench=. -benchtime 0.1s
  $ ./sum.test -test.bench=. -test.benchtime=1s


  • -count n:运行每个测试和基准测试的次数(默认 1),如果 -cpu 指定了,则每个 GOMAXPROCS 值执行 n 次,Examples 总是运行一次。


$ go test -bench=. -count=2
  $ ./sum.test -test.bench=. -test.count=2


  • -cover:开启覆盖分析,开启覆盖分析可能会在编译或测试失败时,代码行数不对。


$ go test -bench=. -cover


-covermode set,count,atomic


  • :覆盖分析的模式,默认是 set,如果设置 -race,将会变为 atomic。


  • set,bool,这个语句运行吗?
  • count,int,该语句运行多少次?
  • atomic,int,数量,在多线程正确使用,但是耗资源的。


  • -coverpkg pkg1,pkg2,pkg3:指定分析哪个包,默认值只分析被测试的包,包为导入的路径。


# sum -> $GOPATH/src/test/sum
  $ go test -coverpkg test/sum


  • -cpu 1,2,4:指定测试或基准测试的 GOMAXPROCS 值。默认为 GOMAXPROCS 的当前值。


  • -list regexp:列出与正则表达式匹配的测试、基准测试或 Examples。只列出顶级测试(不列出子测试),不运行测试。


$ go test -list Sum


  • -parallel n:允许并行执行通过调用 t.Parallel 的测试函数的最大次数。默认值为 GOMAXPROCS 的值。-parallel 仅适用于单个二进制测试文件,但go test命令可以通过指定 -p 并行测试不同的包。查看 go help build


$ go test -run=TestSumParallel -parallel=2


  • -run regexp:只运行与正则表达式匹配的测试和Examples。可以通过 / 来指定测试子函数。go test Foo/A=,会先去匹配并执行 Foo 函数,再查找子函数。


$ go test -v -run TestSumSubTest/1+


  • -short:缩短长时间运行的测试的测试时间。默认关闭。


$ go test -short


  • -timeout d:如果二进制测试文件执行时间过长,panic。默认10分钟(10m)。


$ go test -run TestSumLongTime -timeout 1s


  • -v:详细输出,运行期间所有测试的日志。


$ go test -v


analyze flag


以下测试适用于 go test 和测试二进制文件:


  • -benchmem:打印用于基准的内存分配统计数据。


$ go test -bench=. -benchmem
  $ ./sum.test -test.bench -test.benchmem


  • -blockprofile block.out:当所有的测试都完成时,在指定的文件中写入一个 goroutine 阻塞概要文件。指定 -c,将写入测试二进制文件。


$ go test -v -cpuprofile=prof.out
  $ go tool pprof prof.out


  • -blockprofilerate n:goroutine 阻塞时候打点的纳秒数。默认不设置就相当于 -test.blockprofilerate=1,每一纳秒都打点记录一下。


  • -coverprofile cover.out:在所有测试通过后,将覆盖概要文件写到文件中。设置过 -cover。


  • -cpuprofile cpu.out:在退出之前,将一个 CPU 概要文件写入指定的文件。


  • -memprofile mem.out:在所有测试通过后,将内存概要文件写到文件中。


  • -memprofilerate n:开启更精确的内存配置。如果为 1,将会记录所有内存分配到 profile。


$ go test -memprofile mem.out -memprofilerate 1
  $ go tool pprof mem.out


  • -mutexprofile mutex.out:当所有的测试都完成时,在指定的文件中写入一个互斥锁争用概要文件。指定 -c,将写入测试二进制文件。


  • -mutexprofilefraction n:样本 1 在 n 个堆栈中,goroutines 持有 a,争用互斥锁。


  • -outputdir directory:在指定的目录中放置输出文件,默认情况下,go test 正在运行的目录。


  • -trace trace.out:在退出之前,将执行跟踪写入指定文件。


单元测试


以下是来自wiki对于单元测试的定义


在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。


通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是必须的,但也不坏,这牵涉到项目管理的政策决定。


每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用stubs、mock[1]或fake等测试马甲程序。单元测试通常由软件开发人员编写,用于确保他们所写的代码符合软件需求和遵循开发目标。它的实施方式可以是非常手动的(透过纸笔),或者是做成构建自动化的一部分。


简单来说,单元测试就是程序员自己对于自己的代码进行测试,而一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。


更有一种开发手法,那就是TDD(Test Driven Development),测试驱动开发。期望局部最优到全局最优,这个是一种非常不错的好习惯


请注意这里的局部最优的,局部,并不是函数内的详细。而是整个函数。甚至是一个类,等等。


因为有些函数内部的最优,并非这个函数的最优。这点需要格外的注意。若有兴趣,可了解一下有点关系的贪心算法


测试函数格式


其中参数t用于报告测试失败和附加的日志信息。 testing.T的拥有的方法如下:


func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (t *T) Parallel()
func (t *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool


说了这么多,来实现一个简单的string中的Split函数,并对他进行单元测试,然后在剖析代码。了解单元测试的相关规范


// splits.go
package splitStr
import (
    "strings"
)
// split package with a single split function.
// Split slices s into all substrings separated by sep and
// returns a slice of the substrings between those separators.
func Split(s, sep string) (result []string) {
    i := strings.Index(s, sep)
    for i > -1 {
        result = append(result, s[:i])
        s = s[i+1:]
        i = strings.Index(s, sep)
    }
    result = append(result, s)
    return
}
// split_test.go
package splitStr
import (
    "reflect"
    "testing"
)
// TestSplit 单元测试
func TestSplit(t *testing.T) { // 测试函数名必须以Test开头,必须接收一个*testing.T类型参数
    got := Split("a:b:c", ":")         // 程序输出的结果
    want := []string{"a", "b", "c"}    // 期望的结果
    if !reflect.DeepEqual(want, got) { // 因为slice不能直接比较,借助反射包中的方法比较
        t.Errorf("excepted:%v, got:%#v", want, got) // 测试失败输出错误提示
    }
}
// TestSplit2 单元测试组
func TestSplit2(t *testing.T) {
    // 定义一个测试用例类型
    type test struct {
        input string
        sep   string
        want  []string
    }
    // 定义一个存储测试用例的切片
    tests := []test{
        {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
        {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
        {input: "abcd", sep: "bc", want: []string{"a", "d"}},
    }
    // 遍历切片,逐一执行测试用例
    for _, tc := range tests {
        got := Split(tc.input, tc.sep)
        if !reflect.DeepEqual(got, tc.want) {
            t.Errorf("excepted:%v, got:%#v", tc.want, got)
        }
    }
}


运行结果如下


640 (1).jpg


说明测试成功,本次通过。当然你也可以在Terminal里面直接运行go test,命令,如下所示


640 (2).jpg


温馨提示:关于可能造成运行test不成功原因


直接在split_test.go,运行。


  • 或许知道,go是以文件夹的方法来区分项目。所以当前文件,并不能跑到旁边文件中去找到Split,以至于测试失败。或未达到预期效果


那么正确的打开方式应该是?


在goland中,鼠标右键点击run测试文件所在的文件夹,选择后面第二个 go test projectFileName


Terminal中,应在测试文件所在的文件夹的路径中,进行go test [arge...]


示例看完了,那么进行简单的剖析。先从函数文件说起,(也就是这里的splits.go)


  1. 不在是package main,而是packge projectFileName


  1. 函数名大写,大写意味着公有函数,可支持外部调用


测试文件


  1. 文件名为'*_test.go'


  1. 不在是package main,而是packge projectFileName


  1. 函数名为TestFuncName


基准测试


基准测试函数格式


基准测试就是在一定的工作负载之下检测程序性能的一种方法。基准测试的基本格式如下:


func BenchmarkName(b *testing.B){
    // ...
}


基准测试以Benchmark为前缀,需要一个*testing.B类型的参数b,基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是系统根据实际情况去调整的,从而保证测试的稳定性。 testing.B拥有的方法如下:


func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B) Logf(format string, args ...interface{})
func (c *B) Name() string
func (b *B) ReportAllocs()
func (b *B) ResetTimer()
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool
func (b *B) StartTimer()
func (b *B) StopTimer()
目录
相关文章
|
1月前
|
测试技术 Go
go语言中测试工具
【10月更文挑战第22天】
29 4
|
1月前
|
NoSQL 测试技术 Go
自动化测试在 Go 开源库中的应用与实践
本文介绍了 Go 语言的自动化测试及其在 `go mongox` 库中的实践。Go 语言通过 `testing` 库和 `go test` 命令提供了简洁高效的测试框架,支持单元测试、集成测试和基准测试。`go mongox` 库通过单元测试和集成测试确保与 MongoDB 交互的正确性和稳定性,使用 Docker Compose 快速搭建测试环境。文章还探讨了表驱动测试、覆盖率检查和 Mock 工具的使用,强调了自动化测试在开源库中的重要性。
|
3月前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
110 10
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
4月前
|
监控 Java 测试技术
实战派必看!Python性能测试中,JMeter与Locust如何助力性能调优
【8月更文挑战第6天】性能优化是软件开发的关键。本文介绍JMeter与Locust两款流行性能测试工具,演示如何用于Python应用的性能调优。JMeter可模拟大量用户并发访问,支持多种协议;Locust用Python编写,易于定制用户行为并模拟高并发。根据场景选择合适工具,确保应用在高负载下的稳定运行。
141 4
|
4月前
|
SQL 安全 测试技术
[go 面试] 接口测试的方法与技巧
[go 面试] 接口测试的方法与技巧
|
4月前
|
算法 安全 测试技术
Go - 常用签名算法的基准测试
Go - 常用签名算法的基准测试
35 2
|
5月前
|
运维 监控 测试技术
Golang质量生态建设问题之接入并使用Go单元测试插件的问题如何解决
Golang质量生态建设问题之接入并使用Go单元测试插件的问题如何解决
|
4月前
|
JSON 测试技术 Go
Go 单元测试完全指南(一)- 基本测试流程
Go 单元测试完全指南(一)- 基本测试流程
38 0
|
6月前
|
监控 Java 测试技术
Java性能测试与调优工具使用指南
Java性能测试与调优工具使用指南
|
5月前
|
测试技术 Shell Go
Golang质量生态建设问题之Go语言的单元测试的问题如何解决
Golang质量生态建设问题之Go语言的单元测试的问题如何解决