golang读书笔记(五)

参考了《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.ResponseRecorderhttp.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