单元测试
测试单个文件和单个方法
// 测试当前包下的所有测试文件
go test
// 测试指定文件(测试单个文件,一定要带上被测试的原文件)
go test -v **_test.go **.go
// 测试指定文件下的指定韩式
go test -v **_test.go **.go -test.run FuncName
// 测试单个方法
go test -v -test.run FuncName
例如:
go test -v ./algorithm/luck_envelope_test.go ./algorithm/luck_envelope.go
go test -v ./algorithm/luck_envelope_test.go ./algorithm/luck_envelope.go -test.run TestSimpleRandList
单元测试基本写法
- Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾。
- 测试用例名称一般命名为 Test 加上待测试的方法名。
- 测试用的参数有且只有一个,在这里是 t *testing.T。
- 基准测试(benchmark)的参数是 *testing.B,TestMain 的参数是 *testing.M 类型。
calc.go
package main
func Add(a int, b int) int {
return a + b
}
func Mul(a int, b int) int {
return a * b
}
calc_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
if ans := Add(1, 2); ans != 3 {
t.Errorf("1 + 2 expected be 3, but %d got", ans)
}
if ans := Add(-10, -20); ans != -30 {
t.Errorf("-10 + -20 expected be -30, but %d got", ans)
}
}
testing 的测试用例形式
测试用例有四种形式:
TestXxxx(t *testing.T) // 基本测试用例
BenchmarkXxxx(b *testing.B) // 压力测试的测试用例
Example_Xxx() // 测试控制台输出的例子
TestMain(m *testing.M) // 测试 Main 函数
给个 Example 的例子 :(Example 需要在最后用注释的方式确认控制台输出和预期是不是一致的)
func Example_GetScore() {
score := getScore(100, 100, 100, 2.1)
fmt.Println(score)
// Output:
// 31.1
}
Benchmark 基准测试
概述
基准测试,是一种测试代码性能
的方法,比如你有多种不同的方案,都可以解决问题,那么到底是那种方案性能更好呢?这时候基准测试就派上用场了。
基准测试主要是通过测试CPU和内存的效率
问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。
基准测试基本写法
基准测试
用例的定义如下:
func BenchmarkName(b *testing.B){
// ...
}
函数名必须以 Benchmark
开头,后面一般跟待测试的函数名
参数为 b *testing.B。
执行基准测试时,需要添加 -bench
参数。
例如:
使用 RunParallel
测试并发性能
// 基准测试
func BenchmarkSimpleRandList(b *testing.B) {
count,amount := int64(10), int64(100)
for i := 0; i < b.N; i++ {
SimpleRandList(count,amount)
}
}
// RunParallel 测试并发性能
func BenchmarkSimpleRandList2(b *testing.B) {
count,amount := int64(10), int64(100)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
SimpleRandList(count,amount)
}
})
}
基准测试命令
// 测试所有基准测试函数
go test -bench=.
//测试指定函数的基准测试
go test -bench=原函数名
// 增加内存统计
go test -bench=. -benchmem
// 标志指定运行时间
go test -bench=. -benchtime=20s
例如:
go test -benchmem -bench=.
go test -benchmem -bench=SimpleRandList
go test -bench=SimpleRandList
基准测试结果
循环前耗时配置
如果基准测试在循环前需要一些耗时的配置,则可以先重置定时器:
func BenchmarkBigLen(b *testing.B) {
big := NewBig()
b.ResetTimer()
for i := 0; i < b.N; i++ {
big.Len()
}
}
子测试(Subtests)
子测试是 Go 语言内置支持的,可以在某个测试用例中,根据测试场景使用 t.Run创建不同的子测试用例:
// calc_test.go
func TestMul(t *testing.T) {
t.Run("pos", func(t *testing.T) {
if Mul(2, 3) != 6 {
t.Fatal("fail")
}
})
t.Run("neg", func(t *testing.T) {
if Mul(2, -3) != -6 {
t.Fatal("fail")
}
})
}
之前的例子测试失败时使用 t.Error/t.Errorf
,这个例子中使用 t.Fatal/t.Fatalf
,区别在于前者遇错不停,还会继续执行其他的测试用例,后者遇错即停
。
测试:go test -run TestMul/pos -v
测试命令
func TestFoo(t *testing.T) {
// <setup code>
t.Run("A=1", func(t *testing.T) { ... })
t.Run("A=2", func(t *testing.T) { ... })
t.Run("B=1", func(t *testing.T) { ... })
// <tear-down code>
}
go test -run '' # 执行所有测试。
go test -run Foo # 执行匹配 "Foo" 的顶层测试,例如 "TestFooBar"。
go test -run Foo/A= # 对于匹配 "Foo" 的顶层测试,执行其匹配 "A=" 的子测试。
go test -run /A=1 # 执行所有匹配 "A=1" 的子测试。
运行并验证示例
网络测试
TCP
假设需要测试某个 API 接口的 handler 能够正常工作,例如 helloHandler
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
那我们可以创建真实的网络连接进行测试:
// test code
import (
"io/ioutil"
"net"
"net/http"
"testing"
)
func handleError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal("failed", err)
}
}
func TestConn(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
handleError(t, err)
defer ln.Close()
http.HandleFunc("/hello", helloHandler)
go http.Serve(ln, nil)
resp, err := http.Get("http://" + ln.Addr().String() + "/hello")
handleError(t, err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
handleError(t, err)
if string(body) != "hello world" {
t.Fatal("expected hello world, but got", string(body))
}
}
- net.Listen(“tcp”, “127.0.0.1:0”):监听一个未被占用的端口,并返回 Listener。
- 调用 http.Serve(ln, nil) 启动 http 服务。
- 使用 http.Get 发起一个 Get 请求,检查返回值是否正确。
- 尽量不对 http 和 net 库使用 mock,这样可以覆盖较为真实的场景。
httptest
针对 http 开发的场景,使用标准库 net/http/httptest
进行测试更为高效。
上述的测试用例改写如下:
// test code
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestConn(t *testing.T) {
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
helloHandler(w, req)
bytes, _ := ioutil.ReadAll(w.Result().Body)
if string(bytes) != "hello world" {
t.Fatal("expected hello world, but got", string(bytes))
}
}
使用 httptest 模拟请求对象(req)和响应对象(w)
,达到了相同的目的。
学习链接:
https://books.studygolang.com/The-Golang-Standard-Library-by-Example/chapter09/09.2.html
https://geektutu.com/post/quick-go-test.html#7-Benchmark-%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95
http://shouce.jb51.net/gopl-zh/ch11/ch11-04.html