go读书笔记(五)

韧心222 · · 85 次点击 · · 开始浏览    

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

由于在测试时,未必总是能够得到相应的测试环境,因此有时可以进行一些模拟环境的建立(例如联网),一个有意思的例子可以参考下面的代码:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n223" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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))
}</pre>

在上述代码中,用feed变量表示从服务器返回的RSS XML字符串。

核心的代码则是在函数mockServer中,该函数返回一个httptest.Server类型的指针。在该函数中,首先声明了一个符合http.HandlerFunc签名的匿名函数,使其能够处理http请求。之后,利用httptest.NewServer函数,来创建模拟服务器。创建好了模拟服务器以后,就应该将其测试代码进行合并:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n287" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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)
}
}
}</pre>

1.3 测试服务端点

服务端点(endpoint)是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径。如果在构造网络API,你会希望直接测试自己的服务的所有服务端点,而不用重启整个网络服务。

这段我没看懂,先记录下来什么是测试端点

2 示例

示例主要用于展示某个函数或者方法的正确使用方法,在go中可以通过代码来自动生成godoc中的示例。需要注意的是,示例代码:

  • 必须以Example开头

  • 必须基于已经存在的公开函数或方法

  • 需要将程序最终的输出和示例函数底部的列出的输出做比较。如果两者匹配,这个示例就会作为测试通过,并加入到go的文档中。如果输出不匹配,这个示例就会作为测试失败,例如:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n416" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">fmt.Println(u)
// Output :
// {Bill bill@ardanstudios.com}</pre>

3 基准测试

基准测试是一种测试代码性能的方法,例如可以用于测试解决同一问题的不同方案的性能,就可以使用基准测试。

基准测试的几个要点如下:

  • 文件名必须以_test结尾

  • 必须导入testing

  • 基准测试函数必须以Benchmark开头

  • 基准测试函数接受一个指向testing.B类型的指针作为唯一参数

  • 为了能够准确测试函数的性能,必须在一段时间内反复运行一段代码:

<pre mdtype="fences" cid="n536" lang="go" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">for i := 0; i < b.N; i++{
//测试函数
}</pre>

  • 如果只希望运行基准测试函数,则需要加入-bench选项(该参数接受正则表达式),例如:

<pre mdtype="fences" cid="n548" lang="bash" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">go test -v -run="none" -bench="<名字>"</pre>

  • -benchtime参数可以用于指定测试执行的最短时间

<pre mdtype="fences" cid="n555" lang="bash" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">go test -v -run="none" -bench=<正则表达式> -benchtime="3s"</pre>

  • b.ResetTimer()用于重置计时器

基准测试默认会在1s中之内对需要测试的函数反复调用。

  • -benchmenm用于查看提供每次操作分配内存的次数

附1:HandlerFunc、HandleFunc、Handler

附1.1 http.HandleFunc和http.Handle

http.Handle

http.Handle的函数声明为:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n350" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}</pre>

其中:

  • pattern:路由字符串

  • handler:处理函数

Handler本质上是一个接口,其中包含一个ServeHttp()方法,其定义为:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n358" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}</pre>

http.HandleFunc

http.HandleFunc接受两个参数:

  • pattern:路由字符串

  • handler:func(ResponseWriter, *Request)类型的函数

其定义为:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n367" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}</pre>

其中调用了DefaultServeMux.HandleFunc()函数:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n369" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}</pre>

其中,

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n371" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}</pre>

附1.2 HandlerFunc

HandlerFunc的声明如下:

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="go" cid="n232" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">type HandlerFunc func(ResponseWriter, *Request)</pre>

根据HandlerFunc的声明可知,http.HandlerFunc是一个适配器,其能够使常规函数作为HTTP的请求处理函数。只要函数f具有合适的签名,HandlerFunc(f)就是一个HTTP请求的处理对象。

附2 httptest包

附2.1 httptest.Server

httptest.Server是一个HTTP服务器,它侦听本地环回接口上的系统选择端口,用于端到端的HTTP测试。 其定义为:

<pre spellcheck="false" class="md-fences mock-cm md-end-block" lang="go" cid="n273" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: pre-wrap; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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

}</pre>

附2.2 httptest.NewServer()

httptest.NewServer()启动并返回一个新的服务器。调用者在完成时应该调用Close来关闭它。其声明为:

<pre spellcheck="false" class="md-fences mock-cm md-end-block" lang="go" cid="n284" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: pre-wrap; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">func NewServer(handler http.Handler) *Server</pre>

附2.3 httptest.ResponseRecorder

httptest.ResponseRecorderhttp.ResponseWriter的一个实现,它记录了其突变,以便在测试中进行后续检查。其定义为:

<pre spellcheck="false" class="md-fences mock-cm md-end-block" lang="go" cid="n378" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: pre-wrap; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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

}</pre>

附2.4 httptest.NewRecoder()

生成并返回一个初始化的httptest.ResponseRecorder对象,其声明为:

<pre spellcheck="false" class="md-fences mock-cm md-end-block" lang="go" cid="n387" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Monaco, Consolas, "Andale Mono", "DejaVu Sans Mono", monospace; margin-top: 0px; margin-bottom: 20px; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: pre-wrap; background: rgb(51, 51, 51); position: relative !important; padding: 10px 10px 10px 30px; width: inherit; color: rgb(184, 191, 198); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">func NewRecorder() *ResponseRecorder</pre>


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:韧心222

查看原文:go读书笔记(五)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

85 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传