参考了《Go in Action》、《Go 网络编程:使用 Handler 和 HandlerFunc》《golang中 type func() 用法分析》、《net/http/httptest (net/http) - Go 中文开发手册 - 开发者手册》
1 单元测试
单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。go语言中有几种方法编写单元测试:
- 基础测试(basic test) 只使用一组参数和结果来测试一段代码
- 表组测试(table test) 使用多组参数和结果进行测试,也可以使用一些方法来模仿(mock)测试代码需要的外部资源。
1.1 基础单元测试
-
go的测试工具会认为以
_test.go
为结尾的文件是测试文件 -
在测试时要引入
testing
工具包 -
一个测试函数必须以
Test
开头,并且必须接受一个testing.T
类型的指针,并且不返回任何值
t
的几种常用用法:
-
t.Log()
系列函数:输出测试的消息,如果执行go test
的时候没有加入-v
参数,我们不会看到任何输出(除非执行失败) -
t.Fatal()
系列函数:报告单元测试执行失败,同时会输出一些消息,并立刻停止测试函数的执行 -
t.Error()
系列函数:会输出一些消息,但是不会停止测试函数的执行
如果测试函数没有执行过t.Fatal()
和t.Error()
,就会认为测试执行通过。
1.2 模仿调用(Mocking)
由于在测试时,未必总是能够得到相应的测试环境,因此有时可以进行一些模拟环境的建立(例如联网),一个有意思的例子可以参考下面的代码:
import (
"encoding/xml"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
const checkMark = "\u2713"
const ballotX = "\u2717"
var feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss>
<channel>
<title>Going Go Programming</title>
<description>Golang : https://github.com/goinggo</description>
<link>http://www.goinggo.net/</link>
<item>
<pubDate>Sun, 15 Mar 2015 15:04:00 +0000</pubDate>
<title>Object Oriented Programming Mechanics</title>
<description>Go is an object oriented language.</description>
<link>http://www.goinggo.net/2015/03/object-oriented</link>
</item>
</channel>
</rss>`
func mockServer() *httptest.Server {
f := func(w http.ResponseWriter, r *http.Request){
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/xml")
fmt.Fprintln(w, feed)
}
return httptest.NewServer(http.HandlerFunc(f))
}
在上述代码中,用feed
变量表示从服务器返回的RSS XML字符串。
核心的代码则是在函数mockServer
中,该函数返回一个httptest.Server
类型的指针。在该函数中,首先声明了一个符合http.HandlerFunc
签名的匿名函数,使其能够处理http请求。之后,利用httptest.NewServer
函数,来创建模拟服务器。创建好了模拟服务器以后,就应该将其测试代码进行合并:
func TestDownload(t *testing.T){
statusCode := http.StatusOK
server := mockServer()
defer server.Close()
t.Log("Given the need to test downloading content.")
{
t.Logf("\tWhen Checking \"%s\" for status code \"%d\"", server.URL, statusCode)
{
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal("\t\tShould be able to make the Get call.", ballotX, err)
}
t.Log("\t\tShould be able to make the Get call.", checkMark)
defer resp.Body.Close()
if resp.StatusCode != statusCode{
t.Fatalf("\t\tShould receive a \"%d\" status. %v %v", stautsCode, ballotX, resp.StatusCode)
}
t.Logf("\t\tShould receive a \"%d\" status. %v", statusCode, checkMark)
}
}
}
1.3 测试服务端点
服务端点(endpoint)是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径。如果在构造网络API,你会希望直接测试自己的服务的所有服务端点,而不用重启整个网络服务。
这段我没看懂,先记录下来什么是
测试端点
2 示例
示例主要用于展示某个函数或者方法的正确使用方法,在go中可以通过代码来自动生成godoc中的示例。需要注意的是,示例代码:
- 必须以
Example
开头 - 必须基于已经存在的公开函数或方法
- 需要将程序最终的输出和示例函数底部的列出的输出做比较。如果两者匹配,这个示例就会作为测试通过,并加入到go的文档中。如果输出不匹配,这个示例就会作为测试失败,例如:
fmt.Println(u)
// Output :
// {Bill bill@ardanstudios.com}
3 基准测试
基准测试是一种测试代码性能的方法,例如可以用于测试解决同一问题的不同方案的性能,就可以使用基准测试。
基准测试的几个要点如下:
- 文件名必须以
_test
结尾 - 必须导入
testing
包 - 基准测试函数必须以
Benchmark
开头 - 基准测试函数接受一个指向
testing.B
类型的指针作为唯一参数 - 为了能够准确测试函数的性能,必须在一段时间内反复运行一段代码:
for i := 0; i < b.N; i++{
//测试函数
}
- 如果只希望运行基准测试函数,则需要加入
-bench
选项(该参数接受正则表达式),例如:
go test -v -run="none" -bench="<名字>"
-
-benchtime
参数可以用于指定测试执行的最短时间
go test -v -run="none" -bench=<正则表达式> -benchtime="3s"
-
b.ResetTimer()
用于重置计时器
基准测试默认会在1s中之内对需要测试的函数反复调用。
-
-benchmenm
用于查看提供每次操作分配内存的次数
附1:HandlerFunc、HandleFunc、Handler
附1.1 http.HandleFunc和http.Handle
http.Handle
http.Handle
的函数声明为:
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
其中:
- pattern:路由字符串
- handler:处理函数
Handler本质上是一个接口,其中包含一个ServeHttp()
方法,其定义为:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
http.HandleFunc
http.HandleFunc
接受两个参数:
- pattern:路由字符串
- handler:
func(ResponseWriter, *Request)
类型的函数
其定义为:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
其中调用了DefaultServeMux.HandleFunc()
函数:
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}
其中,
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
附1.2 HandlerFunc
HandlerFunc
的声明如下:
type HandlerFunc func(ResponseWriter, *Request)
根据HandlerFunc的声明可知,http.HandlerFunc
是一个适配器,其能够使常规函数作为HTTP的请求处理函数。只要函数f
具有合适的签名,HandlerFunc(f)
就是一个HTTP请求的处理对象。
附2 httptest包
附2.1 httptest.Server
httptest.Server
是一个HTTP服务器,它侦听本地环回接口上的系统选择端口,用于端到端的HTTP测试。 其定义为:
type Server struct {
URL string // base URL of form http://ipaddr:port with no trailing slash
Listener net.Listener
// TLS is the optional TLS configuration, populated with a new config
// after TLS is started. If set on an unstarted server before StartTLS
// is called, existing fields are copied into the new config.
TLS *tls.Config
// Config may be changed after calling NewUnstartedServer and
// before Start or StartTLS.
Config *http.Server
// contains filtered or unexported fields
}
附2.2 httptest.NewServer()
httptest.NewServer()
启动并返回一个新的服务器。调用者在完成时应该调用Close
来关闭它。其声明为:
func NewServer(handler http.Handler) *Server
附2.3 httptest.ResponseRecorder
httptest.ResponseRecorder
是http.ResponseWriter
的一个实现,它记录了其突变,以便在测试中进行后续检查。其定义为:
type ResponseRecorder struct {
// Code is the HTTP response code set by WriteHeader.
//
// Note that if a Handler never calls WriteHeader or Write,
// this might end up being 0, rather than the implicit
// http.StatusOK. To get the implicit value, use the Result
// method.
Code int
// HeaderMap contains the headers explicitly set by the Handler.
//
// To get the implicit headers set by the server (such as
// automatic Content-Type), use the Result method.
HeaderMap http.Header
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
// contains filtered or unexported fields
}
附2.4 httptest.NewRecoder()
生成并返回一个初始化的httptest.ResponseRecorder
对象,其声明为:
func NewRecorder() *ResponseRecorder
网友评论