用于测试包或者程序的一部分代码或者一组代码的函数,在给定的场景下,有没有按照预期来执行
在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进行解码
最后进行内容的校验