添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
千年单身的警车  ·  The request was ...·  3 年前    · 
考研的丝瓜  ·  TcpClient.GetStream ...·  3 年前    · 
英俊的桔子  ·  idea中Project JDK is ...·  3 年前    · 
粗眉毛的青蛙  ·  如何在Android ...·  3 年前    · 

Mock是一个做自动化测试永远绕不过去的话题。本文主要介绍使用标准库 net/http/httptest 完成HTTP请求的Mock的测试方法。

可能有的小伙伴不太了解mock在实际自动化测试过程中的意义,在我的 另外一篇博客 中有比较详细的描述,在本文中我们可以简单理解为它可以解决测试依赖。下面我们一起来学习它。

http包的HandleFunc函数

我们在前面的文章中介绍过怎么发送各种http请求,但是没有介绍过怎么使用golang启动一个http的服务。我们首先来看看怎么使用golang建立一个服务。

使用golang启动一个http服务非常简单,把下面的代码保存在 httpServerDemo.go 中,执行命令 go run httpServerDemo.go 就完成建立了一个监听在 http://127.0.0.1:9090/ 上的服务。

package main
import (
	"fmt"
	"log"
	"net/http"
func httpServerDemo(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, `{"name":"Bingo","age":"18"}`)
func main() {
	http.HandleFunc("/", httpServerDemo)
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)

访问http://127.0.0.1:9090/可以看到下面的内容。

介绍如何建立一个服务,是因为我们要学习建立服务需要使用到的两个结构体http.Request/http.ResponseWriter。下面我们一起来看看他们的具体内容。

http.Request/http.ResponseWriter

type Request struct {
    Method    string
    URL    *url.URL
    Proto        string
    ProtoMajor    int
    ProtoMinor    int
    Header    Header
    Body    io.ReadCloser
    GetBody    func() (io.ReadCloser, error)
    ContentLength    int64
    TransferEncoding    []string
    Close    bool
type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(int)

从上面的定义可以看到两个结构体具体的参数和方法定义。下面我们一起来学习net/http/httptest。

httptest

假设现在有这么一个场景,我们现在有一个功能需要调用免费天气API来获取天气信息,但是这几天该API升级改造暂时不提供联调服务,而Boss希望该服务恢复后我们的新功能能直接上线,我们要怎么在服务不可用的时候完成相关的测试呢?答案就是使用Mock。

net/http/httptest就是原生库里面提供Mock服务的包,使用它不用真正的启动一个http server(亦或者请求任意的server),而且创建方法非常简单。下面我们一起来看看怎么使用它吧。

定义被测接口

将下面的内容保存到weather.go中:

package weather
import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
const (
	ADDRESS = "shenzhen"
type Weather struct {
	City    string `json:"city"`
	Date    string `json:"date"`
	TemP    string `json:"temP"`
	Weather string `json:"weather"`
func GetWeatherInfo(api string) ([]Weather, error) {
	url := fmt.Sprintf("%s/weather?city=%s", api, ADDRESS)
	resp, err := http.Get(url)
	if err != nil {
		return []Weather{}, err
	if resp.StatusCode != http.StatusOK {
		return []Weather{}, fmt.Errorf("Resp is didn't 200 OK:%s", resp.Status)
	bodybytes, _ := ioutil.ReadAll(resp.Body)
	personList := make([]Weather, 0)
	err = json.Unmarshal(bodybytes, &personList)
	if err != nil {
		fmt.Errorf("Decode data fail")
		return []Weather{}, fmt.Errorf("Decode data fail")
	return personList, nil

根据我们前面的场景设定,GetWeatherInfo依赖接口是不可用的,所以resp, err := http.Get(url)这一行的err肯定不为nil。为了不影响天气服务恢复后我们的功能能直接上线,我们在不动源码,从单元测试用例入手来完成测试。

将下面的内容保存到weather_test.go中::

package weather
import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
var weatherResp = []Weather{
		City:    "shenzhen",
		Date:    "10-22",
		TemP:    "15℃~21℃",
		Weather: "rain",
		City:    "guangzhou",
		Date:    "10-22",
		TemP:    "15℃~21℃",
		Weather: "sunny",
		City:    "beijing",
		Date:    "10-22",
		TemP:    "1℃~11℃",
		Weather: "snow",
var weatherRespBytes, _ = json.Marshal(weatherResp)
func TestGetInfoUnauthorized(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write(weatherRespBytes)
		if r.Method != "GET" {
			t.Errorf("Except 'Get' got '%s'", r.Method)
		if r.URL.EscapedPath() != "/weather" {
			t.Errorf("Except to path '/person',got '%s'", r.URL.EscapedPath())
		r.ParseForm()
		topic := r.Form.Get("city")
		if topic != "shenzhen" {
			t.Errorf("Except rquest to have 'city=shenzhen',got '%s'", topic)
	defer ts.Close()
	api := ts.URL
	fmt.Printf("Url:%s\n", api)
	resp, err := GetWeatherInfo(api)
	if err != nil {
		t.Errorf("ERR:", err)
	} else {
		fmt.Println("resp:", resp)
func TestGetInfoOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write(weatherRespBytes)
		if r.Method != "GET" {
			t.Errorf("Except 'Get' got '%s'", r.Method)
		if r.URL.EscapedPath() != "/weather" {
			t.Errorf("Except to path '/person',got '%s'", r.URL.EscapedPath())
		r.ParseForm()
		topic := r.Form.Get("city")
		if topic != "shenzhen" {
			t.Errorf("Except rquest to have 'city=shenzhen',got '%s'", topic)
	defer ts.Close()
	api := ts.URL
	fmt.Printf("Url:%s\n", api)
	resp, err := GetWeatherInfo(api)
	if err != nil {
		fmt.Println("ERR:", err)
	} else {
		fmt.Println("resp:", resp)

简单解释一下上面的部分代码:

  • 我们通过httptest.NewServer创建了一个测试的http server
  • 通过变量r *http.Request读请求设置,通过w http.ResponseWriter设置返回值
  • 通过ts.URL来获取请求的URL(一般都是http://ip:port)也就是实际的请求url
  • 通过r.Method来获取请求的方法,来测试判断我们的请求方法是否正确
  • 获取请求路径:r.URL.EscapedPath(),本例中的请求路径就是"/weather"
  • 获取请求参数:r.ParseForm,r.Form.Get("city")
  • 设置返回的状态码:w.WriteHeader(http.StatusOK)
  • 设置返回的内容(也就是我们想要的结果):w.Write(personResponseBytes),注意w.Write()接收的参数是[]byte,所以通过json.Marshal(personResponse)转换。
  • 当然,我们也可以设置其他参数的值,也就是我们在最前面介绍的http.Request/http.ResponseWriter这两个结构体的内容。

    在终端中进入我们保存上面两个文件的目录,执行go test -v就可以看到下面的测试结果:

    bingo@Mac httptest$ go test -v
    === RUN   TestGetInfoUnauthorized
    Url:http://127.0.0.1:55816
    --- FAIL: TestGetInfoUnauthorized (0.00s)
            person_test.go:55: ERR:%!(EXTRA *errors.errorString=Resp is didn't 200 OK:401 Unauthorized)
    === RUN   TestGetInfoOK
    Url:http://127.0.0.1:55818
    resp: [{shenzhen 10-22 15℃~21℃ rain} {guangzhou 10-22 15℃~21℃ sunny} {beijing 10-22 1℃~11℃ snow}]
    --- PASS: TestGetInfoOK (0.00s)
    exit status 1
    FAIL    bingo.com/blogs/httptest        0.016s
    

    可以看到两条测试用例成功了一条失败了一条,失败的原因就是我们设置的接口响应码为401(w.WriteHeader(http.StatusUnauthorized)),这个可能会在调用其他服务时遇到,所以有必要进行测试。更多的响应码我们可以在我们的golang安装目录下找到,比如博主的路径是:

    /usr/local/go/src/net/http/status.go
    

    这个文件中定义了几乎所有的http响应码:

        StatusContinue           = 100 // RFC 7231, 6.2.1
    	StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
    	StatusProcessing         = 102 // RFC 2518, 10.1
    	StatusOK                   = 200 // RFC 7231, 6.3.1
    	StatusCreated              = 201 // RFC 7231, 6.3.2
    	StatusAccepted             = 202 // RFC 7231, 6.3.3
    	StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
    	StatusNoContent            = 204 // RFC 7231, 6.3.5
    	StatusResetContent         = 205 // RFC 7231, 6.3.6
    

    综上,我们可以通过不发送httptest来模拟出httpserver和返回值来进行自己代码的测试,上面写的两条用例只是抛砖引玉,大家可以根据实际业务使用更多的场景来进行Mock。

  • httptest
  • HandleFunc
  • 结构体http.Request/http.ResponseWriter
  • http 响应码
  • 参考资料:
    【1】https://wizardforcel.gitbooks.io/golang-stdlib-ref/content/91.html
    【2】https://blog.csdn.net/lavorange/article/details/73369153?utm_source=itdadao&utm_medium=referral