一、单元测试
单元测试是用来测试包或者程序的一部分代码或者一组代码的函数。
在Golang中有几种方法写单元测试,基础测试只使用一组参数和结果来测试一段代码。
表组测试也会测试一段代码,但是会使用多组参数和结果进行测试。也可以使用一些方法来模仿测试代码需要使用到的外部资源。
下面是一个单元测试的例子
listing01_test.go
package mainimport ("net/http""testing"
)const checkMark = "\u2713"
const ballotX = "\u2717"//用来确认http包的Get函数可以下载内容
func TestDownload(t *testing.T) {url := "http://www.goinggo.net/index.xml"statusCode := 200t.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 {t.Fatal("Should be able to make the Get call.", ballotX, err )}t.Log("Should be able to make the Get call.", checkMark)defer resp.Body.Close()if resp.statusCode == statusCode {t.Logf("xxx")}else {t.Errorf("xxx")}}}
}
1、可以运行go test -v来运行这个测试
2、Go语言的测试工具只会认为以test.go结尾的文件是测试文件。
3、一个测试函数必须是公开的函数,并且以Test单词开头。
4、不但函数名字要以Test开头,而且函数的签名必须接收一个指向testing.T类型的指针,并且不返回任何值。
5、如果没有遵守这些约定,测试框架就不会认为这个函数是一个测试函数,也不会让测试工具去执行它。
6、使用t.Log来输出测试的消息。这个方法还有一个名为t.Logf的版本可以格式化消息
7、t.Fatal方法不但报告这个单元测试已经失败,而且会向测试输出写一些消息,而后立刻停止这个测试函数的执行
8、如果状态码匹配,我们就使用t.Logf方法输出信息,否则就使用t.Errorf方法。t.Errorf方法不会停止当前测试函数的执行,所以,如果在第三行之后还有测试,单元测试就会继续执行。
9、如果测试函数执行时没有调用过t.Fatal或者t.Error方法,就会认为测试通过了
二、表组测试
如果测试可以接收一组不同的输入并产生不同的输出的代码,那么应该使用表组测试的方法进行测试。表组测试除了会有一组不同的输入值和期望结果之外,其余部分都很像基础单元测试。测试会依次迭代不同的代码的值,来运行要测试的代码。每次迭代的时候,都会检测返回的结果。这便于在一个函数里测试不同的输入值和条件。
// Sample test to show how to write a basic unit table test.
package listing08import ("net/http""testing"
)const checkMark = "\u2713"
const ballotX = "\u2717"// TestDownload validates the http Get function can download
// content and handles different status conditions properly.
func TestDownload(t *testing.T) {var urls = []struct {url stringstatusCode 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)}}}}
}
三、模仿调用
模仿是一个很常用的技术手段,用来在运行测试时模拟访问不可用的资源,包httptest可以让你能够模仿互联网资源的请求和响应。在我们的单元测试中,通过模仿http.Get的响应,保证在没有网络的情况下测试也不会失败。
// Sample test to show how to mock an HTTP GET call internally.
// Differs slightly from the book to show more.
package listing12import ("encoding/xml""fmt""net/http""net/http/httptest""testing"
)const checkMark = "\u2713"
const ballotX = "\u2717"// feed is mocking the XML document we except to receive.
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 returns a pointer to a server to handle the get call.
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))
}// TestDownload validates the http Get function can download content
// and the content can be unmarshaled and clean.
func TestDownload(t *testing.T) {statusCode := http.StatusOKserver := 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 Documentif 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
}
四、测试服务端点
服务端点是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径