Golang httptest实战教程
创始人
2024-06-03 11:28:02
0

当前首次学习到Golang httptest包时,着实打动了我。其他语言测试HTTP服务需要做很多工作或引用第三方工具,让人不可思议的是,Golang标准库就提供了非常容易理解的测试包。本文介绍httptest包的使用,为你Go http服务构建更好的端到端的测试。

httptest包的理念是,非常容易模拟http服务,也就是说模拟响应写(response writer),提供给http处理器(handle),让我们测试http服务端和客户端很容易。

本文主要介绍两个使用httptest的特定场景: 测试http server处理器,测试http客户端。

测试http服务端处理器

下面通过示例介绍http server的测试。首先看http服务程序,把请求字符串转为大写:

package mainimport ("fmt""log""net/http""net/url""strings"
)// Req: http://localhost:1234/upper?word=abc
// Res: ABC
func upperCaseHandler(w http.ResponseWriter, r *http.Request) {query, err := url.ParseQuery(r.URL.RawQuery)if err != nil {w.WriteHeader(http.StatusBadRequest)fmt.Fprintf(w, "invalid request")return}word := query.Get("word")if len(word) == 0 {w.WriteHeader(http.StatusBadRequest)fmt.Fprintf(w, "missing word")return}w.WriteHeader(http.StatusOK)fmt.Fprintf(w, strings.ToUpper(word))
}func main() {http.HandleFunc("/upper", upperCaseHandler)log.Fatal(http.ListenAndServe(":1234", nil))
}

现在想测试http server使用的upperCaseHandler逻辑,我们需要准备两方面:

  • 使用httptest.NewRequest暴露的函数创建http.Request对象,NewRequest返回Request, 可以传给http.Handler进行测试.

  • 使用httptest.NewRecorder函数创建http.ResponseWriter,返回httptest.ResponseRecorder。ResponseRecorder是
    http.ResponseWriter 的实现,它记录变化为了后面测试检查.

httptest.ResponseRecorder

httptest.ResponseRecorder是 http.ResponseWriter 的实现,可以传给http server handle,记录所有处理并写回响应的数据,下面测试程序可以看到其如何实现:

package mainimport ("io/ioutil""net/http""net/http/httptest""testing"
)func TestUpperCaseHandler(t *testing.T) {req := httptest.NewRequest(http.MethodGet, "/upper?word=abc", nil)w := httptest.NewRecorder()upperCaseHandler(w, req)res := w.Result()defer res.Body.Close()data, err := ioutil.ReadAll(res.Body)if err != nil {t.Errorf("expected error to be nil got %v", err)}if string(data) != "ABC" {t.Errorf("expected ABC got %v", string(data))}
}

上面示例中首先定义请求和响应,然后传入处理器进行测试。然后检查ResponseRecorder的Result方法输出:

func (rw *ResponseRecorder) Result() *http.Response

Result返回处理器生成的响应。返回相应至少有StatusCode, Header, Body, 以及可选其他内容,未来可能会填充更多字段,所以调用者在测试中不应该深度比较相等。

测试HTTP客户端

测试服务端处理器相对容易,特别当测试处理器逻辑时,仅需要在测试中模拟http.ResponseWriter 和 http.Request对象。对于HTTP客户端测试,情况稍晚有点复杂。原因是有时不容易模拟或复制整个HTTP Server,请看下面示例:

package mainimport ("io/ioutil""net/http""github.com/pkg/errors"
)type Client struct {url string
}func NewClient(url string) Client {return Client{url}
}func (c Client) UpperCase(word string) (string, error) {res, err := http.Get(c.url + "/upper?word=" + word)if err != nil {return "", errors.Wrap(err, "unable to complete Get request")}defer res.Body.Close()out, err := ioutil.ReadAll(res.Body)if err != nil {return "", errors.Wrap(err, "unable to read response data")}return string(out), nil
}

client需要url,表示远程服务端基地址。然后调用/upper,带上输入单词,最后返回结果字符串给调用者,如果调用不成功还返回错误对象。为了测试这段代码,需要模拟整个http服务端逻辑,或至少是响应请求路径:/upper。使用httptest包可以模拟整个http 服务,通过初始化本地服务,监听回环地址并返回你想要的任何内容。

使用 httptest.Server

  • 通过调用httptest.NewServer函数生成我们想要的 httptest.Server。表示http服务,监听回环地址及可选的端口号,用于实现端到端HTTP测试。
func NewServer(handler http.Handler) *Server

NewServer 启动并返回新的HTTP服务,调用者使用完成后应该调用Close方法结束服务。下面通过示例进行解释:

package mainimport ("fmt""net/http""net/http/httptest""strings""testing"
)func TestClientUpperCase(t *testing.T) {expected := "dummy data"svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, expected)}))defer svr.Close()c := NewClient(svr.URL)res, err := c.UpperCase("anything")if err != nil {t.Errorf("expected err to be nil got %v", err)}// res: expected\r\n// due to the http protocol cleanup responseres = strings.TrimSpace(res)if res != expected {t.Errorf("expected res to be %s got %s", expected, res)}
}

上面示例中使用httptest.NewServer函数创建了模拟http服务器,给它传入自定义模拟处理器,总是返回相同的数据。并使用服务端url作为客户端请求url,从而模拟并让服务端返回任何我们想测试的内容。

当然我们可以修改处理器,让其返回我们期望的逻辑:


func TestClientUpperCase(t *testing.T) {svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {query, err := url.ParseQuery(r.URL.RawQuery)if err != nil {w.WriteHeader(http.StatusBadRequest)fmt.Fprintf(w, "invalid request")return}word := query.Get("word")if len(word) > 0 {fmt.Fprintf(w, strings.ToUpper(word))} else {fmt.Fprintf(w, "no input")}}))defer svr.Close()expected := "ANYTHING"c := NewClient(svr.URL)res, err := c.UpperCase("anything")if err != nil {t.Errorf("expected err to be nil got %v", err)}// res: expected\r\n// due to the http protocol cleanup responseres = strings.TrimSpace(res)if res != expected {t.Errorf("expected res to be %s got %s", expected, res)}
}

总结

本文介绍httptest包,可以很方便测试http服务端处理逻辑,以及模拟http服务端测试客户端请求逻辑。由于很方面模拟,从而可以把一组参数和期望值进行组合,循环进行测试并对比结果,可以极大地提升测试效率。

上一篇:Python中的Requests库

下一篇:四等分list

相关内容

热门资讯

安卓系统对比骁龙,性能与生态的... 你有没有想过,为什么你的手机里装的是安卓系统,而不是苹果的iOS呢?又或者,为什么你的安卓手机里搭载...
qt程序安卓系统运行,基于Qt... 你有没有想过,为什么有些手机上的程序运行得那么顺畅,而有些却总是卡得让人抓狂?今天,就让我来给你揭秘...
安卓系统免费应用推荐,助你畅享... 手机里的应用是不是越来越多,有时候都挑花眼了呢?别急,今天我就来给你推荐一些安卓系统上的免费应用,让...
安卓系统视频通话app,打造无... 你有没有发现,现在手机上的视频通话功能越来越强大了?尤其是安卓系统上的那些视频通话app,简直让人爱...
安卓系统发现高危病毒,守护手机... 亲爱的手机用户们,最近可是有个大消息在安卓系统用户群里炸开了锅!没错,就是安卓系统发现了一款高危病毒...
安卓系统疯狂弹广告,揭秘广告软... 你有没有遇到过这种情况?手机里突然弹出一个广告,让你瞬间心情大崩溃?没错,说的就是安卓系统那让人头疼...
ebook 10进入安卓系统 你有没有发现,最近你的安卓手机里多了一个新伙伴——那就是电子书(ebook)10!没错,就是那个我们...
安卓系统如何调听筒,安卓系统调... 手机听筒声音突然变小了?别急,让我来教你如何轻松调教安卓系统的听筒,让它重新恢复活力!一、检查音量设...
安卓系统是怎么手机,解锁智能生... 你有没有想过,我们每天不离手的安卓手机,它背后的安卓系统究竟是怎么一回事呢?今天,就让我带你一探究竟...
安卓系统能代替windows系... 你有没有想过,我们日常使用的安卓系统和Windows系统,哪个才是真正的霸主呢?是不是有时候觉得安卓...
lp108安卓系统,功能特点与... 你有没有听说最近LP108安卓系统火得一塌糊涂?没错,就是那个让无数手机用户都为之疯狂的新系统!今天...
安卓系统挂载u盘,轻松实现数据... 你有没有想过,你的安卓手机或平板电脑突然变成了一个移动的U盘?没错,就是那种可以随意存取文件的神奇设...
i5 安卓系统,引领智能终端新... 你有没有想过,为什么你的手机总是卡得要命,而别人的手机却能流畅如丝?是不是因为你的手机搭载了那个传说...
安卓手机系统没有升级,揭秘潜在... 你有没有发现,你的安卓手机系统好像好久没升级了呢?是不是觉得有点out了?别急,今天就来给你详细聊聊...
安卓14系统定制v,创新功能与... 你知道吗?最近安卓系统又出新花样了!安卓14系统定制版V,这名字听起来就让人兴奋不已。今天,就让我带...
手机安卓系统越高越好,探索最新... 你有没有发现,每次手机更新系统,你的手机就像脱胎换骨了一样?没错,说的就是你,那个安卓手机!今天,咱...
鸿蒙系统怎么用回安卓,轻松实现... 你是不是也和我一样,对鸿蒙系统的新鲜感还没过,却又忍不住想回到熟悉的安卓世界?别急,今天就来手把手教...
苹果7跟安卓系统,性能对决与用... 你有没有想过,为什么苹果7那么受欢迎,而安卓系统却有着庞大的用户群体?今天,我们就来聊聊这个话题,看...
安卓手机刷简化系统,轻松实现流... 你有没有想过,你的安卓手机其实可以变得更加轻快、流畅呢?没错,就是通过刷简化系统!今天,就让我带你一...
社保掌上通安卓系统,轻松掌握在... 你有没有发现,现在的生活越来越离不开手机了?无论是购物、聊天还是办公,手机都能轻松搞定。这不,今天就...