【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 ...
随机推荐
- JVM内存模型:程序计数器
一.JVM模型概述 java虚拟机(JVM)在java程序运行的过程中,会将它所管理的内存划分为若干个不同的数据区域,这些区域有的随着JVM的启动而创建,有的随着用户线程的启动和结束而建立和销毁.一个 ...
- mongodbtemplate配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- Spring 自动扫描 不支持jar包 <component-scan>
问题描述: 本机windows myeclipse10.7 运行非maven java项目,其中部分类使用spring3 注解进行注入,使用配置文件component-scan进行扫描,在本机debu ...
- oracle中使用函数控制过程是否执行(结合job使用)
oracle中使用函数控制过程是否执行(结合job使用时候,循环时间不好写的时候,可以此种方法比较方便) CREATE OR REPLACE FUNCTION wsbs_pk_date_validat ...
- iOS开发-OpenGL ES入门教程1
http://www.jianshu.com/p/750fde1d8b6a 这里是一篇新手教程,环境是Xcode7+OpenGL ES 2.0,目标写一个OpenGL ES的hello world.O ...
- Linux 搭建FTP
Linux 搭建FTP 步骤一:安装 vsftpd 1,运行以下命令安装 vsftpd. yum install -y vsftpd 出现下图表示安装成功. 2,打开etc/vsftpd cd /et ...
- linux虚拟机中安装vm_tool的方法及用处
解决问题:实现虚拟机VMware上linux与windows互相自由复制与粘贴.如在同一个系统下ctrl+c 与ctrl+v一样方便.解决了只能通过U盘摆渡复制的繁琐问题. 系统环境: 虚拟机VMwa ...
- 20165310java_teamExp1_week1
结对编程项目-四则运算-week1 需求分析 第一周达成 支持真分数的四则运算 支持多运算符 能手动输入n道题目,n由使用者输入 后续拓展的可能 能随机生成n道题目,n由使用者输入 能够判断正误,错误 ...
- IMAP协议命令(详细)
参照:http://www.cnblogs.com/qiubole/archive/2007/11/23/970180.html 转载:http://blog.sina.com.cn/s/blog_5 ...
- BZOJ 1049 数字序列(LIS)
题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1049 题意:给出一个数列A,要求:(1)修改最少的数字使得数列严格递增:(2)在( ...