测试
-
少DEBUG,多Testing
-
go语言采用表格驱动测试
-
分离的测试数据和测试逻辑
-
明确的出错信息
-
可以部分失败
-
go语言的语法使得我们更容易实践表格驱动测试
// 如果要测试add函数,那么文件名就叫做add_test.go
// 测试方法名字就叫做TestAdd
package main
import "testing"
func add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
tests := []struct{
a, b, c int }{
{
3, 4, 7},
{
1, 2, 3},
{
4, 5, 1}, //模拟出错
{
1, 1, 1}, // 模拟出错
}
for _, tt := range tests {
if actual := add(tt.a, tt.b); actual != tt.c {
t.Errorf("%d + %d got %d ;expected %d ", tt.a, tt.b, tt.c, actual)
}
}
}
- 控制台
GOROOT=/usr/local/gogosetup
GOPATH=/Users/xiao_xiaoxiao/gogosetup
/usr/local/go/bin/go test -c -o /private/var/folders/db/mpycn34j5534hjnxy33y6h9m0000gn/T/___TestAdd_in_learn2_testing learn2/testinggosetup
/usr/local/go/bin/go tool test2json -t /private/var/folders/db/mpycn34j5534hjnxy33y6h9m0000gn/T/___TestAdd_in_learn2_testing -test.v -test.run ^TestAdd$gosetup
=== RUN TestAdd
TestAdd: add_test.go:19: 4 + 5 got 1 ;expected 9
TestAdd: add_test.go:19: 1 + 1 got 1 ;expected 2
--- FAIL: TestAdd (0.00s)
FAIL
用命令行测试
- 在当前目录下使用go test命令
不通过
通过
代码覆盖率
- go语言可以检测代码覆盖率
这个项目里面的代码都会统计覆盖率
用命令行操作
- go test -coverprofile=c.out 生成文件
- go tool cover -html=c.out 页面观看覆盖率
- 注意: test文件要和逻辑代码在同一个包中 不然显示不出来
性能测试
// BenchmarkXxx testing.B
func BenchmarkAdd(b *testing.B) {
a, j, c := 1, 2, 3 //一般性能测试选择最复杂的那个用例,
// 如果在这里做测试用的数据的生成,不想把这个时间加进去,可以在生成数据之后reset一下时间
b.ResetTimer()
for i := 0; i < b.N; i++ {
//性能测试一般循环多次,具体不用我们定,用b.N系统自动帮我们确定多少次
actual := Add(a, j)
if actual != c {
b.Errorf("%d + %d got %d ;expected %d ", a, j, c, actual)
}
}
}
控制台
GOROOT=/usr/local/gogosetup
GOPATH=/Users/xiao_xiaoxiao/gogosetup
/usr/local/go/bin/go test -c -o /private/var/folders/db/mpycn34j5534hjnxy33y6h9m0000gn/T/___BenchmarkAdd_in_learn2_testing learn2/testinggosetup
/private/var/folders/db/mpycn34j5534hjnxy33y6h9m0000gn/T/___BenchmarkAdd_in_learn2_testing -test.v -test.bench ^BenchmarkAdd$ -test.run ^$gosetup
goos: darwin
goarch: amd64
pkg: learn2/testing
BenchmarkAdd
# 100000000次 每次操作 0.512 ns
BenchmarkAdd-4 1000000000 0.512 ns/op
PASS
用命令行
gotest -bench .
性能调优
- go test -bench . -cpuprofile=cpu.out生成二进制文件
- 用pprof查看这个二进制文件go tool pprof cpu.out:进入pprof环境
- 进入pprof环境之后,使用web命令生成图(要先安装graphviz工具)
- 这张图会告诉你时间消耗在哪个地方,箭头越粗,方框越大越耗时
(大概长这样,我的用例太简单了,所以截图了其他的用例的图)
- 然后通过看这张图,看哪里可以优化,优化完再次运行,然后不断优化到满意为止
http测试
- 分为2种,一种是测试这个函数是否正确,虚构http请求的request和response,然后测试函数对各种返回是否正常,第二种是测试服务器是否正常,构造一个server,然后通过通信,看看是否正常
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
// 测试errWrapper
// 入参是appHandler :::type appHandler func(writer http.ResponseWriter, request *http.Request) error
// func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
// }
// ******************* 入参构造 ***********************//
// 模拟抛出错误
func errPanic(_ http.ResponseWriter, _ *http.Request) error {
panic(123)
}
type testingUserError string
func (e testingUserError) Error() string {
return e.Message()
}
func (e testingUserError) Message() string {
return string(e)
}
// 模拟用户错误
func errUserError(_ http.ResponseWriter, _ *http.Request) error {
return testingUserError("user error")
}
//模拟。。。
func errNotFound(_ http.ResponseWriter, _ *http.Request) error {
return os.ErrNotExist
}
func errNoPermission(_ http.ResponseWriter, _ *http.Request) error {
return os.ErrPermission
}
func errUnknown(_ http.ResponseWriter, _ *http.Request) error {
return errors.New("unknown error")
}
// 没有错误
func noError(writer http.ResponseWriter, _ *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}
// 这个是 测试用例
var tests = []struct {
h appHandler // 入参
// 返回的信息
code int
message string
}{
{
errPanic, 500, "Internal Server Error"},
{
errUserError, 400, "user error"},
{
errNotFound, 404, "Not Found"},
{
errNoPermission, 403, "Forbidden"},
{
errUnknown, 500, "Internal Server Error"},
{
noError, 200, "no error"},
}
//测试errWrapper
// 只是测试errWrapper这个函数
func TestErrWrapper(t *testing.T) {
for _, tt := range tests {
f := errWrapper(tt.h) //入参
// httptest 可以虚构request 和 response
// response request 是 指针类型
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://www.imooc.com", nil)
f(response, request) // 执行
verifyResponse(response.Result(), tt.code, tt.message, t)
}
}
// 测试整个服务器是否正常,这个是真的发请求,
func TestErrWrapperInServer(t *testing.T) {
for _, tt := range tests {
f := errWrapper(tt.h)
// 发请求 构造一个server
server := httptest.NewServer(
http.HandlerFunc(f)) // 传入处理函数
resp, _ := http.Get(server.URL)
verifyResponse(resp, tt.code, tt.message, t)
}
}
// 判断 是否正确
func verifyResponse(resp *http.Response, expectedCode int, expectedMsg string, t *testing.T) {
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n")
if resp.StatusCode != expectedCode ||
body != expectedMsg {
t.Errorf("expect (%d, %s); "+
"got (%d, %s)",
expectedCode, expectedMsg,
resp.StatusCode, body)
}
}
生成文档和示例代码
文档
- go语言的文档生成用godoc
godoc命令要自己安装
- 第一种:直接在命令行查看
go doc 显示struct结构 和引入哪些包
# package queue // import "imooc.com/ccmouse/learngo/lang/queue"
# type Queue []int
go doc Push方法
#package queue // import "."
# func (q *Queue) Push(v int)
# Pushes the element into the queue.
go doc Queue
# package queue // import "."
#type Queue []int
# A FIFO queue.
#func (q *Queue) IsEmpty() bool
#func (q *Queue) Pop() int
#func (q *Queue) Push(v int)
go help doc
- 在页面中看
godoc -http :8080
注释#
- go文档的注释是可以随便写的
package queue
// A FIFO queue.
type Queue []int
// Pushes the element into the queue. 注释会记录下来
// e.g. q.Push(123) 会缩进而且用框框框起来
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
// Pops element from head.
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
// Returns if the queue is empty or not.
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
example
func ExampleQueue_Pop() {
q := Queue{
1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
// Output:
// 1
// 2
// false
// 3
// true
}
格式正确的话是可以运行的,在代码中不要有多余的注释,不然运行不起来的
-生成文件
总结#
- 用注释写文档
- 在测试中加入Example
- 用go doc/godoc来查看/生成文档