前言

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/上的服务。

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net/http"
  6. )
  7. func httpServerDemo(w http.ResponseWriter, r *http.Request) {
  8. fmt.Fprintf(w, `{"name":"Bingo","age":"18"}`)
  9. }
  10. func main() {
  11. http.HandleFunc("/", httpServerDemo)
  12. err := http.ListenAndServe(":9090", nil)
  13. if err != nil {
  14. log.Fatal("ListenAndServe: ", err)
  15. }
  16. }

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

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

http.Request/http.ResponseWriter

  1. type Request struct {
  2. Method string
  3. URL *url.URL
  4. Proto string
  5. ProtoMajor int
  6. ProtoMinor int
  7. Header Header
  8. Body io.ReadCloser
  9. GetBody func() (io.ReadCloser, error)
  10. ContentLength int64
  11. TransferEncoding []string
  12. Close bool
  13. ...
  1. type ResponseWriter interface {
  2. Header() Header
  3. Write([]byte) (int, error)
  4. WriteHeader(int)
  5. }

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

httptest

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

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

定义被测接口

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

  1. package weather
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. )
  8. const (
  9. ADDRESS = "shenzhen"
  10. )
  11. type Weather struct {
  12. City string `json:"city"`
  13. Date string `json:"date"`
  14. TemP string `json:"temP"`
  15. Weather string `json:"weather"`
  16. }
  17. func GetWeatherInfo(api string) ([]Weather, error) {
  18. url := fmt.Sprintf("%s/weather?city=%s", api, ADDRESS)
  19. resp, err := http.Get(url)
  20. if err != nil {
  21. return []Weather{}, err
  22. }
  23. if resp.StatusCode != http.StatusOK {
  24. return []Weather{}, fmt.Errorf("Resp is didn't 200 OK:%s", resp.Status)
  25. }
  26. bodybytes, _ := ioutil.ReadAll(resp.Body)
  27. personList := make([]Weather, 0)
  28. err = json.Unmarshal(bodybytes, &personList)
  29. if err != nil {
  30. fmt.Errorf("Decode data fail")
  31. return []Weather{}, fmt.Errorf("Decode data fail")
  32. }
  33. return personList, nil
  34. }

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

测试代码

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

  1. package weather
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/http/httptest"
  7. "testing"
  8. )
  9. var weatherResp = []Weather{
  10. {
  11. City: "shenzhen",
  12. Date: "10-22",
  13. TemP: "15℃~21℃",
  14. Weather: "rain",
  15. },
  16. {
  17. City: "guangzhou",
  18. Date: "10-22",
  19. TemP: "15℃~21℃",
  20. Weather: "sunny",
  21. },
  22. {
  23. City: "beijing",
  24. Date: "10-22",
  25. TemP: "1℃~11℃",
  26. Weather: "snow",
  27. },
  28. }
  29. var weatherRespBytes, _ = json.Marshal(weatherResp)
  30. func TestGetInfoUnauthorized(t *testing.T) {
  31. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  32. w.WriteHeader(http.StatusUnauthorized)
  33. w.Write(weatherRespBytes)
  34. if r.Method != "GET" {
  35. t.Errorf("Except 'Get' got '%s'", r.Method)
  36. }
  37. if r.URL.EscapedPath() != "/weather" {
  38. t.Errorf("Except to path '/person',got '%s'", r.URL.EscapedPath())
  39. }
  40. r.ParseForm()
  41. topic := r.Form.Get("city")
  42. if topic != "shenzhen" {
  43. t.Errorf("Except rquest to have 'city=shenzhen',got '%s'", topic)
  44. }
  45. }))
  46. defer ts.Close()
  47. api := ts.URL
  48. fmt.Printf("Url:%s\n", api)
  49. resp, err := GetWeatherInfo(api)
  50. if err != nil {
  51. t.Errorf("ERR:", err)
  52. } else {
  53. fmt.Println("resp:", resp)
  54. }
  55. }
  56. func TestGetInfoOK(t *testing.T) {
  57. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  58. w.WriteHeader(http.StatusOK)
  59. w.Write(weatherRespBytes)
  60. if r.Method != "GET" {
  61. t.Errorf("Except 'Get' got '%s'", r.Method)
  62. }
  63. if r.URL.EscapedPath() != "/weather" {
  64. t.Errorf("Except to path '/person',got '%s'", r.URL.EscapedPath())
  65. }
  66. r.ParseForm()
  67. topic := r.Form.Get("city")
  68. if topic != "shenzhen" {
  69. t.Errorf("Except rquest to have 'city=shenzhen',got '%s'", topic)
  70. }
  71. }))
  72. defer ts.Close()
  73. api := ts.URL
  74. fmt.Printf("Url:%s\n", api)
  75. resp, err := GetWeatherInfo(api)
  76. if err != nil {
  77. fmt.Println("ERR:", err)
  78. } else {
  79. fmt.Println("resp:", resp)
  80. }
  81. }

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

  • 我们通过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就可以看到下面的测试结果:

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

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

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

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

  1. StatusContinue = 100 // RFC 7231, 6.2.1
  2. StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
  3. StatusProcessing = 102 // RFC 2518, 10.1
  4. StatusOK = 200 // RFC 7231, 6.3.1
  5. StatusCreated = 201 // RFC 7231, 6.3.2
  6. StatusAccepted = 202 // RFC 7231, 6.3.3
  7. StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
  8. StatusNoContent = 204 // RFC 7231, 6.3.5
  9. StatusResetContent = 205 // RFC 7231, 6.3.6
  10. ...

综上,我们可以通过不发送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测试的更多相关文章

  1. 【Golang 接口自动化06】微信支付md5签名计算及其优化

    前言 可能看过我博客的朋友知道我主要是做的支付这一块的测试工作.而我们都知道现在比较流行的支付方式就是微信支付和支付宝支付,当然最近在使用低手续费大力推广的京东金融(已改名为京东数科)以后也可能站到第 ...

  2. 【Golang 接口自动化00】为什么要用Golang做自动化?

    为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后,因为没有开发那么多的时间和项目来实践,怕步此前学习Java缺少练习遗忘殆尽的后尘,决定利用工作之余的时间把此前用Python的写的 ...

  3. 【Golang 接口自动化04】 解析接口返回JSON串

    前言 上一次我们一起学习了如何解析接口返回的XML数据,这一次我们一起来学习JSON的解析方法. JSON(Javascript Object Notation)是一种轻量级的数据交换语言,以文字为基 ...

  4. 【python接口自动化】- 使用requests库发送http请求

    前言:什么是Requests ?Requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库.它⽐ urllib 更加⽅便,可以节约我们⼤ ...

  5. python+pytest接口自动化(12)-自动化用例编写思路 (使用pytest编写一个测试脚本)

    经过之前的学习铺垫,我们尝试着利用pytest框架编写一条接口自动化测试用例,来厘清接口自动化用例编写的思路. 我们在百度搜索天气查询,会出现如下图所示结果: 接下来,我们以该天气查询接口为例,编写接 ...

  6. 【Golang 接口自动化02】使用标准库net/http发送Post请求

    写在前面 上一篇我们介绍了使用 net/http 发送get请求,因为考虑到篇幅问题,把Post单独拎了出来,我们在这一篇一起从源码来了解一下Golang的Post请求. 发送Post请求 net/h ...

  7. 【Golang 接口自动化01】使用标准库net/http发送Get请求

    发送Get请求 使用Golang发送get请求很容易,我们还是使用http://httpbin.org作为服务端来进行演示. package main import ( "bytes&quo ...

  8. 【Golang 接口自动化05】使用yml管理自动化用例

    我们在前面几篇文章中学习怎么发送数据请求,怎么处理解析接口返回的结果,接下来我们一起来学习怎么进行测试用例管理,今天我们介绍的是使用yml文件进行用例管理,所以首先我们一起来了解一下YAML和它的简单 ...

  9. 【Golang 接口自动化03】 解析接口返回XML

    上一篇我们学习了怎么发送各种数据类型的http请求,这一篇我们来介绍怎么来解析接口返回的XML的数据. 解析接口返回数据 定义结构体 假设我们现在有一个接口返回的数据resp如下: <?xml ...

随机推荐

  1. Leetcode: Binary Tree Inorder Transversal

    Given a binary tree, return the inorder traversal of its nodes' values. For example: Given binary tr ...

  2. Fiddler过滤指定域名

    Fiddler过滤指定域名的方法一 切换到fiddler右侧窗口的Filters选项卡,勾选顶部的“Use Filters”,找到Hosts区域,设置以下三个选项: 1.第一项有三个选项,不做更改: ...

  3. kendo 级联加带搜索的下拉框以及js赋值

    1‘.js给下拉框赋值 $("#UserRole").data("kendoDropDownList").value(dataItem.RoleName); $ ...

  4. Python Data Science Toolbox Part 1 Learning 1 - User-defined functions

    User-defined functions from:https://campus.datacamp.com/courses/python-data-science-toolbox-part-1/w ...

  5. Linux基础命令---dumpe2fs

    dumpe2fs 显示ext2.ext3.ext4文件系统的超级快和块组信息.此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora. 1.语法 ...

  6. Linux基础命令---fsck

    fsck 检查或者修复指定的文件系统,可以是设备名.挂载点,还可以是一个ext2的label,或者是一个UUID.此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.ope ...

  7. 性能优化之MySQL调优篇

    MySQL对于很多Linux从业者而言,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰.在进行MySQL的优化之前必须要了解的就是MySQL的查询过程,很多的查询优化工作 ...

  8. 20145310《网络对抗》Exp2 后门原理与实践

    实验内容 (1)使用netcat获取主机操作Shell,cron启动,使用socat获取主机操作Shell, 任务计划启动. (2)使用MSF meterpreter生成可执行文件,利用ncat或so ...

  9. codevs & vijos 爱在心中 - Tarjan

    描述 “每个人都拥有一个梦,即使彼此不相同,能够与你分享,无论失败成功都会感动.爱因为在心中,平凡而不平庸,世界就像迷宫,却又让我们此刻相逢Our Home.” 在爱的国度里有N个人,在他们的心中都有 ...

  10. POJ 1845 Sumdiv(求因数和 + 逆元)题解

    题意:给你a,b,要求给出a^b的因子和取模9901的结果. 思路:求因子和的方法:任意A = p1^a1 * p2^a2 ....pn^an,则因子和为sum =(1 + p1 + p1^2 + . ...