用于测试包或者程序的一部分代码或者一组代码的函数,在给定的场景下,有没有按照预期来执行

在Go中写单元测试有几种方法

基础测试:单一组参数和结果来测试一段代码

表组测试:测试一段代码,但是使用了多组参数和结果来测试

接下来我们分开来看这些例子

1,基础单元测试

// 简单的单元测试,文件命名后缀为_test.go

package listing01

import (

“net/http”

//引入了testing的包

“testing”

)

//对号

const checkMark = “\u2713”

//叉号

const ballotX = “\u2717”

// 测试下载,这个测试函数必须要以Test开头,接收一个testing.T的值,不返回任何值

func TestDownload(t *testing.T) {

url := “http://www.goinggo.net/feeds/posts/default?alt=rss”

statusCode := 200

t.Log(“Given the need to test downloading content.”)

{

//格式化输出日志

t.Logf(“\tWhen checking \”%s\” for status code \”%d\””, url, statusCode)

{

resp, err := http.Get(url)

if err != nil {

//如果GET失败了,t.Fatal方法会说明这个测试函数失败了,停止这个函数的执行

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.Logf(“\t\tShould receive a \”%d\” status. %v”,

statusCode, checkMark)

} else {

//报告函数执行失败了,但不停止函数的执行

t.Errorf(“\t\tShould receive a \”%d\” status. %v %v”,

statusCode, ballotX, resp.StatusCode)

}

}

}

}

1.测试文件的文件名是要携带 _test.go结尾的文件

如果不遵从这个方式,go就会找不到测试的函数

2.测试函数,必须要接收一个指向testing.T的指针,并且不返回任何的值

3.如果需要看到全部的输出,可以使用go test 加上-v的选项

2.表组测试

接收一组不同的输入,来进行测试的代码,就是所谓的表组测试

表组测试除了有一组不同的输入值和期望结果外,其他部分都很像是基础单元测试

测试会迭代的测试不同的值获得及其的相应,我们来看一下表组测试的例子

代码基本如下

// 表组测试的基本书写

package listing08

import (

“net/http”

“testing”

)

const checkMark = “\u2713”

const ballotX = “\u2717”

// 循环测试不同的状态

func TestDownload(t *testing.T) {

var urls = []struct {

url string

statusCode int

}{

{

“http://www.goinggo.net/feeds/posts/default?alt=rss”,

http.StatusOK,

},

{

“http://rss.cnn.com/rss/cnn_topstbadurl.rss”,

http.StatusNotFound,

},

}

t.Log(“Given the need to test downloading different content.”)

{

for _, u := range urls {

t.Logf(“\tWhen checking \”%s\” for status code \”%d\””,

u.url, u.statusCode)

{

resp, err := http.Get(u.url)

if err != nil {

t.Fatal(“\t\tShould be able to Get the url.”,

ballotX, err)

}

t.Log(“\t\tShould be able to Get the url.”,

checkMark)

defer resp.Body.Close()

if resp.StatusCode == u.statusCode {

t.Logf(“\t\tShould have a \”%d\” status. %v”,

u.statusCode, checkMark)

} else {

t.Errorf(“\t\tShould have a \”%d\” status. %v %v”,

u.statusCode, ballotX, resp.StatusCode)

}

}

}

}

}

我们直接使用之前的测试函数,接收一个指向testing T类型的指针,然后我们还设置了一个结构数组,存储的状态里面,第一个字段是URL,第二个字段是我们请求资源后期望收到的状态码

我们接下来看下如何让表组进行测试工作的

我们直接迭代表组中的值,使用不同的URL来测试代码

测试的代码和基础单元测试的代码相同

不过我么使用的是表组内的值进行的测试

我们使用上面的结构数组来进行表组测试

3.模仿调用

利用互联网来记性测试的流程是必不可少的,但是如果没法利用互联网测试,那么会很棘手

为了修正互联网的问题,标准库中出现了一个名为httptest的包

模仿基于HTTP的网络调用,运行时候测试网络访问,也就是上面的http.Get的调用

并获得预期的响应,我们看下基础单元测试,并改为模仿调用goinggo.net网站的RSS列表,如下所示

//用于内部模仿HTTP GET调用

package listing12

import (

“encoding/xml”

“fmt”

“net/http”

“net/http/httptest”

“testing”

)

const checkMark = “\u2713”

const ballotX = “\u2717″

// 模拟我们期望收到的XML文档

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>`

// mockServer返回用于处理请求服务器的指针

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)

}

//作为NewServer

return httptest.NewServer(http.HandlerFunc(f))

}

// 确定http包的GET函数可以下载内容,并被反序列化

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”,

statusCode, ballotX, resp.StatusCode)

}

t.Logf(“\t\tShould receive a \”%d\” status. %v”,

statusCode, checkMark)

var d Document

if err := xml.NewDecoder(resp.Body).Decode(&d); err != nil {

t.Fatal(“\t\tShould be able to unmarshal the response.”,

ballotX, err)

}

t.Log(“\t\tShould be able to unmarshal the response.”,

checkMark)

if len(d.Channel.Items) == 1 {

t.Log(“\t\tShould have \”1\” item in the feed.”,

checkMark)

} else {

t.Error(“\t\tShould have \”1\” item in the feed.”,

ballotX, len(d.Channel.Items))

}

}

}

}

// Item defines the fields associated with the item tag in

// the buoy RSS document.

type Item struct {

XMLName xml.Name `xml:”item”`

Title string `xml:”title”`

Description string `xml:”description”`

Link string `xml:”link”`

}

// Channel defines the fields associated with the channel tag in

// the buoy RSS document.

type Channel struct {

XMLName xml.Name `xml:”channel”`

Title string `xml:”title”`

Description string `xml:”description”`

Link string `xml:”link”`

PubDate string `xml:”pubDate”`

Items []Item `xml:”item”`

}

// Document defines the fields associated with the buoy RSS document.

type Document struct {

XMLName xml.Name `xml:”rss”`

Channel Channel `xml:”channel”`

URI string

}

首先在上面,我们声明了一个名为mockServer的函数,函数利用了httptest包内的支持来模拟对互联网上真实服务器的调用

// mockServer返回用于处理请求服务器的指针

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)

}

//作为NewServer

return httptest.NewServer(http.HandlerFunc(f))

}

f为一个handlerFunc函数的类型,

type HandlerFunc func(ResponseWriter, *Request)

是一个适配器,允许常规函数作为HTTP的处理函数使用

然后我们通过http.GET调用来使用这个模仿处理器

http.GET调用的时候,执行的是处理函数

我们在这个处理函数中设置了状态码200,设置了请求头为Content-Type,结尾是application/xml

然后就是模仿服务器和基础单元测试如何整合在一起的,以及如何将http.Get发送到模拟服务器

我们直接调用了

resp,err := http.Get(server.URL)

但是实际上,我们的调用是使用了localhost作为了地址,端口是52065

一个随机的端口

4.测试服务端点

我们首先编写一个服务端点的接口,我们利用一个程序来提供网络服务

首先是入口类

func main() {

handlers.Routes()

log.Println(“listener : Started : Listening on :4000”)

http.ListenAndServe(“:4000”, nil)

}

我们开启了Routes函数,为托管的网络服务设置了一个服务端点

并设置了服务监听的端口,启动了网络服务,等待请求

然后是handlers包的代码,代码基本如下

// handlers提供了用于网络服务的服务端点

package handlers

import (

“encoding/json”

“net/http”

)

// Routes为网络服务设置路由,利用了HandleFunc函数

func Routes() {

http.HandleFunc(“/sendjson”, SendJSON)

}

// SendJSON return一个简单的JSON文档

func SendJSON(rw http.ResponseWriter, r *http.Request) {

u := struct {

Name string

Email string

}{

Name: “Bill”,

Email: “bill@ardanstudios.com”,

}

rw.Header().Set(“Content-Type”, “application/json”)

rw.WriteHeader(200)

json.NewEncoder(rw).Encode(&u)

}

我们利用HandleFunc来配置路由,将URL映射到对应的处理代码

将/sendjson这个uri和sendJson函数绑定在一起

在SendJSON中,声明了一个HandlerFunc的函数签名

设置了一个结构类型,创建了一个变量,设置了初始值

利用json写入到了rw中这个结构

然后是一个test的go文件书写

// 测试内部服务的端口

package handlers_test

import (

“encoding/json”

“net/http”

“net/http/httptest”

“testing”

“../handlers”

)

const checkMark = “\u2713”

const ballotX = “\u2717”

func init() {

handlers.Routes()

}

// 测试/sendjson内部服务端点

func TestSendJSON(t *testing.T) {

t.Log(“Given the need to test the SendJSON endpoint.”)

{

req, err := http.NewRequest(“GET”, “/sendjson”, nil)

if err != nil {

t.Fatal(“\tShould be able to create a request.”,

ballotX, err)

}

t.Log(“\tShould be able to create a request.”,

checkMark)

rw := httptest.NewRecorder()

http.DefaultServeMux.ServeHTTP(rw, req)

if rw.Code != 200 {

t.Fatal(“\tShould receive \”200\””, ballotX, rw.Code)

}

t.Log(“\tShould receive \”200\””, checkMark)

u := struct {

Name string

Email string

}{}

if err := json.NewDecoder(rw.Body).Decode(&u); err != nil {

t.Fatal(“\tShould decode the response.”, ballotX)

}

t.Log(“\tShould decode the response.”, checkMark)

if u.Name == “Bill” {

t.Log(“\tShould have a Name.”, checkMark)

} else {

t.Error(“\tShould have a Name.”, ballotX, u.Name)

}

if u.Email == “bill@ardanstudios.com” {

t.Log(“\tShould have an Email.”, checkMark)

} else {

t.Error(“\tShould have an for Email.”, ballotX, u.Email)

}

}

}

我们仍然是以_test文件结尾,对于测试文件,只能访问本包中公开的标识符

在测试文件中

我们设置了init为服务端初始化路由

func init() {

handlers.Routes()

}

然后我们访问/sendjson接口,进行单元测试

我们创建一个Request值,利用GET方法请求sendjson服务端点的相应,因为是GET方法,第三个发送数据的参数就不需要传入

还创建了一个http.ResponseRecorder值

这样就有了Request和http.ResponseRecoder这两个值

我们就可以检查这个响应的内容

利用ServeHttp方法,完成了对/sendjson服务端点的请求

然后检查响应的状态和内容

如果是200,就利用JSON进行解码

最后进行内容的校验

发表评论

邮箱地址不会被公开。 必填项已用*标注