【Golang 接口自动化08】使用标准库httptest完成HTTP请求的Mock测试
前言
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)
FAIL
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
【Golang 接口自动化08】使用标准库httptest完成HTTP请求的Mock测试的更多相关文章
- 【Golang 接口自动化06】微信支付md5签名计算及其优化
前言 可能看过我博客的朋友知道我主要是做的支付这一块的测试工作.而我们都知道现在比较流行的支付方式就是微信支付和支付宝支付,当然最近在使用低手续费大力推广的京东金融(已改名为京东数科)以后也可能站到第 ...
- 【Golang 接口自动化00】为什么要用Golang做自动化?
为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后,因为没有开发那么多的时间和项目来实践,怕步此前学习Java缺少练习遗忘殆尽的后尘,决定利用工作之余的时间把此前用Python的写的 ...
- 【Golang 接口自动化04】 解析接口返回JSON串
前言 上一次我们一起学习了如何解析接口返回的XML数据,这一次我们一起来学习JSON的解析方法. JSON(Javascript Object Notation)是一种轻量级的数据交换语言,以文字为基 ...
- 【python接口自动化】- 使用requests库发送http请求
前言:什么是Requests ?Requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库.它⽐ urllib 更加⽅便,可以节约我们⼤ ...
- python+pytest接口自动化(12)-自动化用例编写思路 (使用pytest编写一个测试脚本)
经过之前的学习铺垫,我们尝试着利用pytest框架编写一条接口自动化测试用例,来厘清接口自动化用例编写的思路. 我们在百度搜索天气查询,会出现如下图所示结果: 接下来,我们以该天气查询接口为例,编写接 ...
- 【Golang 接口自动化02】使用标准库net/http发送Post请求
写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单独拎了出来,我们在这一篇一起从源码来了解一下Golang的Post请求. 发送Post请求 net/h ...
- 【Golang 接口自动化01】使用标准库net/http发送Get请求
发送Get请求 使用Golang发送get请求很容易,我们还是使用http://httpbin.org作为服务端来进行演示. package main import ( "bytes&quo ...
- 【Golang 接口自动化05】使用yml管理自动化用例
我们在前面几篇文章中学习怎么发送数据请求,怎么处理解析接口返回的结果,接下来我们一起来学习怎么进行测试用例管理,今天我们介绍的是使用yml文件进行用例管理,所以首先我们一起来了解一下YAML和它的简单 ...
- 【Golang 接口自动化03】 解析接口返回XML
上一篇我们学习了怎么发送各种数据类型的http请求,这一篇我们来介绍怎么来解析接口返回的XML的数据. 解析接口返回数据 定义结构体 假设我们现在有一个接口返回的数据resp如下: <?xml ...
随机推荐
- Django中的admin组件分析
admin的使用介绍 django-admin的使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.可以在项目的 setting ...
- Twitter OA prepare: Flipping a bit
You are given a binary array with N elements: d[0], d[1], ... d[N - 1]. You can perform AT MOST one ...
- Leetcode: Binary Tree Level Order Transversal
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, ...
- 001-Two Sum
Given an array of integers, return indices of the two numbers such that they add up to a specific ta ...
- discuz财付通也阵亡了
今日做交易部分,然后焦头烂额. 首先这积分,威望,金钱,什么鬼,乱七八糟的...... 然后这支付宝,啊,,,,,竟然停止个人接口了,不得已要使用财付通. %&……*&……&不 ...
- VS2010/MFC编程入门之四十(文档、视图和框架:各对象之间的关系)
前面一节中鸡啄米进行了文档.视图和框架的概述,本节主要讲解文档.视图.框架结构中各对象之间的关系. 各个对象之间的关系 文档.视图.框架结构中涉及到的对象主要有:应用程序对象.文档模板对象.文档对象. ...
- Learning to Rank算法介绍:RankNet,LambdaRank,LambdaMart
之前的博客:http://www.cnblogs.com/bentuwuying/p/6681943.html中简单介绍了Learning to Rank的基本原理,也讲到了Learning to R ...
- 最近整理出了有关大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互等1.7G的学习资料,有视频教程,源码,课件,工具,面试题等等。这里将珍藏多年的资源免费分享给各位小伙伴们
大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互 领取方式在篇尾!!! 基础篇.互联网架构,高级程序员必备视频,Linux系统.JVM.大型分布式电商项目实战视频...... ...
- # 20155327 2016-2017-3 《Java程序设计》第5周学习总结
20155327 2016-2017-3 <Java程序设计>第5周学习总结 教材学习内容总结 理解异常架构 粉红色的是受检查的异常(checked exceptions),其必须被 tr ...
- 解决input标签placeholder属性浏览器兼容性问题的一种方法
为文本框input添加文字输入提示,H5为input提供了一个placeholder属性.在支持H5的浏览器中,用此属性设置输入提示,简单方便,但是对于IE8以下版本,都不支持placeholder属 ...