golang 之单元测试

测试

  测试是自动化测试的简称,即编写简单的程序来确保程序(产品代码)在测试中针对特定输入产生预期的输出。Go的测试方法看上去相对比较低级,它依赖于命令go test和一些能用go test运行的测试函数的编写约定

go test 工具

  go test 子命令是Go语言包的测试驱动程序,这些包根据某些约定组织在一起。在一个包目录中,以_test.go结尾的文件不是go build命令编译的目标,而是go test编译的目标。

  在*_test.go文件中,三种函数需要特殊对待,即

  1. 功能测试函数
  2. 基准测试函数 :名称以benchmark开头,用来检测某些操作的性能。
  3. 示例运行测试函数 :以Example来测试某些操作的性能。用来提供机器检查过的文档。

Test 函数

  每一个测试文件必须导入testing包,签名如下

import "testing"

func TestName(t *testing.T)  {
	
}

 功能测试函数必须以Test开头,可选的后缀名称必须以大写字母开头:

import "testing"

func TestSin(t *testing.T)  {
	
}

func TestCos(t *testing.T)  {
	
}

示例 

  如下编译一个测试连接mysql的单元测试,必须保证同目录的情况下,否则找不到,创建一个mysql.go用来编写连接的代码

package mysql

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

func findByPk(pk int) int {
    var num int = 0
    db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/plugin?charset=utf8")
    if err != nil {
        panic(err.Error())
    }
    defer db.Close()
    stmtOut, err := db.Prepare("select id from t_admin where id=?")
    if err != nil {
        panic(err.Error())
    }
    defer stmtOut.Close()

    err = stmtOut.QueryRow(pk).Scan(&num)
    if err != nil {
        panic(err.Error())
    }
    return num
}

 在同目录下创建一个mysql_test.go用来编写测试代码

package mysql

import (
    "testing"
)

func Test_findByPk(t *testing.T) {
    num := findByPk(1)
    t.Log(num)
}

 运行

  • go test: 在当前目录下运行所有测试用例 
  • go test -file *_test.go : 运行指定测试文件。如 go test -file mysql_test.go
  • go test -V: 显示详细结果
  • go test -run="Test_XXX" : 指定运行某个方法

测试覆盖率

  一个测试套件覆盖待测试包的比例称为测试的覆盖率。覆盖率无法直接通过数量来衡量。任何事情都是动态的。即使最微小的程序都无法精准的测量。语句覆盖率是指部分语句在一次执行中至少执行一次。go语言中提供cover工具

# go test -cover
PASS
coverage: 100.0% of statements
ok    jdGoBackend/library/sdk/jd      0.017s

  go还提供了将输出打印报告的方法。

# go test -cover -coverprofile=c.out

 然后可以通过html方法显示,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。

# go tool cover -html=c.out  

基准测试 

  基准测试是在一定的工作负载之下检测程序性能的一种方法,在Go里面,基准测试函数看上去像一个测试函数。但前缀是Benchmark并且拥有一个*testing.B参数用来提供大多数和*testing.T相同的方法。额外增加一些与性能检测相关的方法。还提供了一个整数检测成员N,用来检测执行的次数。

  基本格式

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

  下面的基准测试在一个循环中调用了IsPaling共 N次

import "testing"

func BenchmarkIsPaling(b *testing.B)  {
	for i:=0; i< b.N; i++{
		IsPaling("aaaaa")
	}
}

 和测试不同的是,默认情况下不会执行任何基准测试,需要标记-bench的参数指定要运行的基准参数。

#  go test -bench=IsPaling
BenchmarkIsPaling-8        10000000               203 ns/op
PASS
ok      jdGoBackend/library/sdk/jd      2.255s

  就上面的运行结果,基础名称BenchmarkIsPaling-8后缀8表示GOMAXPROCS的值。这个数字对并发基准测试很重要。

  报告告诉我们每次IsPaling调用耗费0.23ms。这个是1 000 000 0次调用的平均值

我们可以通过-benchmen再报告中包含了内存分配统计数据。这里和优化之前的内存分配进行比较。

# go test -bench=. -benchmen  // . 通配符匹配所有
PASS 
BenchmarkIsPaling  1000000 1026 ns/op 304 B/op 4 allocs/op

 最后,性能比较函数知识普通的代码,它们的表现形式通常是带有一个参数的函数,被多个不同Benchmark函数传入不同的值来调用。如下

func benchmark(b *testing.B, size int){/* ... */}
func Benchmark10(b *testing.B){ benchmark(b, 10) }
func Benchmark100(b *testing.B){ benchmark(b, 100) }
func Benchmark1000(b *testing.B){ benchmark(b, 1000) }

 参数size指定了输入的大小,每个Benchmark函数传入的值都不同但是在每个函数内部是一个常量,不要使用b.N作为输入的大小。除非把它当作固定大小输入的循环次数。否则该基准测试的结果毫无意义。

最后测试牵扯到性能,将会在性能模块详细介绍。

posted @ 2020-03-03 23:07  Dwyane.wang  阅读(1030)  评论(0编辑  收藏  举报