一. go语言单元测试写法
2. 测试的方法名以Test开头. 参数为(test *Test)
例:
package TestingDebug func Add(a,b int) int { return b + a }
package TestingDebug import ( "fmt" testing" ) func TestAdd(t *testing.T) { var test = []struct{ a,b,c }{ {1,2,1)">3},{3,1)">5,1)">84,1)">5for _,tt := range test { if actural := Add(tt.a,tt.b) ; actural != tt.c { fmt.Printf(%d + %d,except:%d,actual:%d,tt.a,tt.b,tt.c,actural) } } }
需要注意几点
2. 测试文件与源文件在同一个目录下, 否则技术不出来测试代码覆盖率.
二. 如何测试代码覆盖率
然后点击TestingDebug进入文件夹,点击测试的文件. 看右侧绿线,表示测试覆盖的代码. 这里覆盖了100%
三. 性能测试BenchMark
性能测试使用Testing.B
比如,我们想要测试一个复杂一些的数据的累加,看看性能如何.
/** * 性能测试 */ func BenchmarkAdd(bb *testing.B) { var a,c a = 123 b = 4557 c = 4680 for i := 0; i<bb.N ; i++ { if actural := Add(a,b) ; actural != c { fmt.Printf(} }
bb.N表示的是系统自动计算的一个循环次数,我们不用自己指定.
goos: darwin goarch: amd64 pkg: aaa/TestingDebug BenchmarkAdd-8 1000000000 0.317 ns/op PASS
以上是测试结果. 1000000000 代表测试的次数是10亿次. 0.317 ns/op每个操作执行的时间是0.317ns
四. 性能调优--使用pprof进行性能优化
如上一步,我们对代码进行了性能测试. 每个操作执行时间是0.317ns. 那么,假如有一个稍微复杂一些的例子,我如何看出花费了这么多时间,都是在哪个步骤花的? 哪个步骤耗费时间最多呢?
我们使用命令来看
执行上面的命令生成一个cpu.out的文件,查看cpu运行的情况
cpu.out是一个二进制文件,我们没办法打开. 我们可以使用命令打开
go tool pprof cpu.out
这里我们可以通过pprof的web视图来查看到底那个步骤耗时最多. 但是看红色字,查看web视图,需要安装一个软件Graphviz
安装Graphviz,下载地址: http://www.graphviz.org/. 我是mac,下载一个mac版本的:
下载命令: brew install graphviz
安装成功以后,数据命令 go tool pprof cpu.out,然后输入web,会生产一个svg文件,用浏览器打开我们就会看到效果了
这就是对简单程序的一个分析. 他在每个阶段用时是多少. 因为我们这里是很简单的一个demo,所以耗时很少了.
五. 测试http服务器
http测试分为两种
1. 通过使用假的Request/Response实现 : 运行速度更快,可以测试更多种情况,更像单元测试.
2. 通过启服务器: 模拟的诊室服务,覆盖代码更多.
1. 使用假的Request和Response模拟http请求实现单元测试
分析:
首先: 明确我们的目标,要对谁做测试. 我们要对封装的错误处理进行测试.
第二: 测试有输入和输出. 预计输入输出,真实输入输出. 我们这里输入是谁呢?通过包装类WrapError分析,入参是对http请求业务逻辑的处理,出参是包装的错误处理,包括错误码和错误消息
所以,单元测试的表格的结构体怎么写呢?
test := []{ h appHandler // 入参 Code int 出参 Message string 出参 } { }
入参是一个appHandler函数. 因此我们定义一个函数,来模拟返回的错误信息. 异常的种类有很多,先定义一个panic
func handlerPanic(writer http.ResponseWriter,request *http.Request) error { panic(panic error) }
接下来模拟http请求,判断异常处理是否正确,完整代码如下:
package main import ( io/IoUtilnet/httpnet/http/httpteststrings ) func handlerPanic(writer http.ResponseWriter,request *) } func TestWrapError(t * 我们测试的异常的封装方法 那么对于单元测试来说,无非就是入参和出参 test := []{ h appHandler 入参 Code 出参 Message } { {handlerPanic,500,1)">Internal Server Error range test { 要测试的方法WrapError f := WrapError(tt.h) 模拟Request和response response := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet,1)">http://www.baidu.com读取reponse返回的body b,_ := IoUtil.ReadAll(response.Body) body := strings.Trim(string(b),1)">\n) 测试返回值和预期是否一致 if tt.Code != response.Code || tt.Message != body { t.Errorf(预期返回--code:%d,message:%s \n 实际返回--code:%d,message:%s用户自定义异常,我们在测试用户自定义信息) //用户自定义异常 type testUserError string func (u testUserError) Error() string{ return u.Message() } func (u testUserError) Message() string { return string(u) } func handlerUserError(writer http.ResponseWriter,1)">http.Request) error { return testUserError(user error) } func handlerPanic(writer http.ResponseWriter,{handlerUserError,400,"user error"}, } 代码覆盖率 35%. 接下来补全测试代码,提高代码覆盖率github.com/pkg/errorsos ) type testUserError string func (u testUserError) Error() { return u.Message() } func (u testUserError) Message() return (u) } func handlerUserError(writer http.ResponseWriter,1)">) } func handlerPanicError(writer http.ResponseWriter,1)">) } func handlerNotFountError(writer http.ResponseWriter,1)"> os.ErrNotExist } func handlerForbiddenError(writer http.ResponseWriter,1)"> os.ErrPermission } func otherError(writer http.ResponseWriter,1)">return errors.New(123) } func noError(writer http.ResponseWriter,1)"> nil } func TestWrapError(t * } { {handlerPanicError,{handlerUserError,1)">400,{handlerNotFountError,1)">404,1)">Not Found403,1)">Forbidden200,1)">""代码覆盖率
达到了80%,只有最后的main方法没有被覆盖. ok了
2. 使用真实的http请求进行测试
真实http请求 func TestWrapErrorForServer(t * WrapError(tt.h) s := httptest.NewServer(http.HandlerFunc(f)) 这里url不用填自己的,httptest在new server的时候帮我们定义好了一个url response,1)"> s.Client().Get(s.URL) b,_ :=) if tt.Code != response.StatusCode || tt.Message !=except--code: %d,message: %s \n actual--code:%d,response.StatusCode,body) } } }模拟数据的部分,两种类型的http请求是一样的,被提取到外面了,最终完整代码如下:
nil } 测试数据 } { {handlerPanicError,} 模拟http请求 func TestWrapError(t *读取reponse返回的body verify(response.Result(),tt,t) } } WrapError(tt.h) s := s.Client().Get(s.URL) verify(response,t) } } func verify(response *http.Response,tt struct {h appHandler;Code int;Message string},t *testing.T) { b,1)"> IoUtil.ReadAll(response.Body) body := strings.Trim() body { t.Errorf(package fileListener import ( ) type UserError func (u UserError) Error() u.Message() } func (u UserError) Message() (u) } func FileHandler(writer http.ResponseWriter,1)"> 获取url路径,路径是/list/之后的部分 if c := strings.Index(request.URL.Path,1)">/list/"); c != 0 { return UserError(url 不是已list开头) } path := request.URL.Path[len():] 打开文件 file,err := os.Open(path) if err != nil { err } defer file.Close() 读出文件 b,1)"> IoUtil.ReadAll(file) err } 写入文件到页面 writer.Write(b) nil }aaa/errorhandling/filelistenerserver/fileListenergithub.com/kelseyhightower/confd/log ) 定义一个函数类型的结构,返回值是erro type appHandler func(writer http.ResponseWriter,1)">http.Request) error 封装error func WrapError(handler appHandler) func (http.ResponseWriter,*http.Request) { return func(writer http.ResponseWriter,1)">http.Request) { defer func(){ if r := recover(); r != nil { log.Info(发生异常) http.Error(writer,http.StatusText(http.StatusInternalServerError),http.StatusInternalServerError) } }() 执行原来的逻辑. 然后增加error的错误处理 err := handler(writer,1)"> nil { if userErr,ok := err.(userError); ok { http.Error(writer,userErr.Message(),http.StatusBadRequest) } code := http.StatusOK switch { case os.IsNotExist(err): code = http.StatusNotFound os.IsPermission(err): code = http.StatusForbidden default: code = http.StatusInternalServerError } http.Error(writer,http.StatusText(code),code) } } } type userError interface { error 系统异常 Message() 用户自定义异常 } 我们来模拟一个web服务器. 在url上输入一个地址,然后显示文件内容 做一个显示文件的web server func main() { http.HandleFunc(/ 监听端口:8888 err := http.ListenAndServe(:8888if err != nil { panic(err) } }代码结构