前言
net/http/httptest
可能有的小伙伴不太了解mock在实际自动化测试过程中的意义,在我的另外一篇博客中有比较详细的描述,在本文中我们可以简单理解为它可以解决测试依赖。下面我们一起来学习它。
http包的HandleFunc函数
我们在前面的文章中介绍过怎么发送各种http请求,但是没有介绍过怎么使用golang启动一个http的服务。我们首先来看看怎么使用golang建立一个服务。
httpServerDemo.gogo run httpServerDemo.gohttp://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.Requesthttp.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/httptesthttp 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
}
GetWeatherInforesp, err := http.Get(url)
测试代码
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.Requesthttp.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)
FAIL
exit status 1
FAIL bingo.com/blogs/httptest 0.016s
w.WriteHeader(http.StatusUnauthorized)
/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。
总结
http.Requesthttp.ResponseWriter
参考资料:
【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