Golang单元测试
testing
import "testing"
Golang支持的测试种类
类型 | 格式 | 作用 |
---|---|---|
单元测试 | 函数名前缀为Test | 测试程序的一些逻辑行为是否正确 |
基准(压力)测试 | 函数名前缀为Benchmark | 测试函数的性能 |
示例测试 | 函数名前缀为Example | 为文档提供示例文档 |
模糊(随机)测试 | 函数名前缀为Fuzz | 生成一个随机测试用例去覆盖认为测不到的各种赋值场景 |
单元测试
_test.go
calc.gocalc.goAddMulcalc_test.go
go mod init
实例演示:
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)
}
}
Testt *testing.T *testing.B *testing.M
run test
Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestADD$ go_pro
ok go_pro 0.272s
go test
PS E:\golang开发学习\go_pro> go test
PASS
ok go_pro 0.244s
PASS表示测试用例运行成功,FAIL表示测试用例运行失败
go test -v-v-cover
PS E:\golang开发学习\go_pro> go test -v
=== RUN TestADD
--- PASS: TestADD (0.00s)
PASS
ok go_pro 0.245s
PS E:\golang开发学习\go_pro> go test -cover
PASS
coverage: 50.0% of statements
ok go_pro 0.417s
TestADD-run*^$
PS E:\golang开发学习\go_pro> go test -run TestADD -v
=== RUN TestADD
--- PASS: TestADD (0.00s)
PASS
ok go_pro 0.180s
单元测试框架提供的日志方法
方法 | 作用 |
---|---|
Log | 打印日志,同时结束测试 |
Logf | 格式化打印日志,同时结束测试 |
Error | 打印错误日志,同时结束测试 |
Errorf | 格式化打印错误日志,同时结束测试 |
Fatal | 打印致命日志,同时结束测试 |
Fatalf | 格式化打印致命日志,同时结束测试 |
子测试(Subtests)
t.Run
calc_test.go
func TestMul(t *testing.T) {
t.Run("pos", func(t *testing.T) { // pos参数为子测试命名
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.Errorft.Fatal/t.Fatalft.Error/t.Errorft.Fatal/t.Fatalf
运行结果:
Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestMul$ go_pro
ok go_pro 0.288s
运行某个测试用例的子测试:
PS E:\golang开发学习\go_pro> go test -run TestMul/pos -v
=== RUN TestMul
=== RUN TestMul/pos
--- PASS: TestMul (0.00s)
--- PASS: TestMul/pos (0.00s)
PASS
ok go_pro 0.256s
table-driven tests
func TestMul(t *testing.T) {
cases := []struct {
Name string
A, B, Expected int
}{
{"pos", 2, 3, 6},
{"neg", 2, -3, -6},
{"zero", 2, 0, 0},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if ans := Mul(c.A, c.B); ans != c.Expected {
t.Fatalf("%d * %d expected %d, but %d got", c.A, c.B, c.Expected, ans)
}
})
}
}
cases
- 新增用例方便
- 测试代码可读性好
- 用例失败时,报错信息格式较统一,测试报告易读
若数据很大,或者一些二进制数据,推荐使用相对路径从文件中读取
帮助函数(helpers)
对一些重复的逻辑,抽取出来作为公共的帮助函数可以增加测试代码的可读性和可维护性
借助帮助函数可以让测试用例的主逻辑看起来更加清晰
例如,将创建子测试的逻辑抽取出来:
type calcCase struct {
A, B, Expected int
}
func createMulTestCase(t *testing.T, c *calcCase) {
// t.Helper()
if ans := Mul(c.A, c.B); ans != c.Expected {
t.Fatalf("%d * %d expected %d, but %d got", c.A, c.B, c.Expected, ans)
}
}
func TestMul(t *testing.T) {
createMulTestCase(t, &calcCase{2, 3, 6})
createMulTestCase(t, &calcCase{2, -3, -6})
createMulTestCase(t, &calcCase{2, 0, 1}) // 错误case
}
这里故意创建了一个错误的测试用例,运行之后会报告发生错误的文件信息:
Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestMul$ go_pro
--- FAIL: TestMul (0.00s)
e:\golang开发学习\go_pro\factory_test.go:22: 2 * 0 expected 1, but 0 got
FAIL
FAIL go_pro 0.259s
FAIL
createMulTestCaseTestMulcreateMulTestCaset.Helper()
t.Helper()
func createMulTestCase(t *testing.T, c *calcCase) {
t.Helper()
if ans := Mul(c.A, c.B); ans != c.Expected {
t.Fatalf("%d * %d expected %d, but %d got", c.A, c.B, c.Expected, ans)
}
}
运行结果:
Running tool: C:\Go\bin\go.exe test -timeout 30s -run ^TestMul$ go_pro
--- FAIL: TestMul (0.00s)
e:\golang开发学习\go_pro\factory_test.go:29: 2 * 0 expected 1, but 0 got
FAIL
FAIL go_pro 0.208s
FAIL
t.Errort.Fatalt.Helper()
setup和teardown
setupteardown
testing
func setup() {
fmt.Println("before all tests")
}
func teardown() {
fmt.Println("after all tests")
}
func Test1(t *testing.T) {
fmt.Println("test1")
}
func Test2(t *testing.T) {
fmt.Println("test2")
}
func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}
Test1Test2TestMainTestMain(m)m.Run()os.Exit(code)m.Run()
go test
$ go test
before all tests
test1
test2
PASS
after all tests
ok example 0.003s
网络测试(Network)
TCP/HTTP
假设要测试一个API接口的handler能够正常工作,例如helloHandler
package main
import "net/http"
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
则可以创建真是的网络链接来进行测试:
package main
import (
"io/ioutil"
"net"
"net/http"
"testing"
)
func handlerError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal("failed", err)
}
}
func TestConn(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
handlerError(t, err)
defer l.Close()
http.HandleFunc("/hello", helloHandler)
go http.Serve(l, nil)
resp, err := http.Get("http://" + l.Addr().String() + "/hello")
handlerError(t, err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
handlerError(t, err)
if string(body) != "hello world" {
t.Fatal("expected hello world but got", string(body))
}
}
net.Listen("tcp", "127.0.0.1:0")http.Serve(l, nil)http.Gethttpnetmock
httptest
net/http/httptest
可将上述测试用例修改为:
package main
import (
"io/ioutil"
"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),也达到了相同的目的