去简单的 http 处理程序测试所有路径

我试图在这个简单的 http 处理程序文件上获得 100% 的代码覆盖率。


如果成功,该文件将写入默认响应标头,然后返回 200 并带有我在下面测试过的“Pong”。但是,也有可能写入默认标头会产生错误,在这种情况下,预计会出现带有内部错误正文的 500 响应。


我正在努力弄清楚如何在测试中触发 500 响应案例。如果由于某种原因将 writeDefaultHeaders 函数调用的第二个参数更改为“html”,例如因为 html 不是我的服务中支持的响应内容类型,则该案例将失败。


在代码中模拟这个调用/点击这个错误分支的惯用方法是什么?


谢谢。


ping_handler_test.go


package main


import (

    "net/http"

    "net/http/httptest"

    "testing"

)


func Test200PingHandler(t *testing.T) {

    req, _ := http.NewRequest("GET", "/ping", nil)

    w := httptest.NewRecorder()


    PingHandler(w, req)


    if w.Code != http.StatusOK {

        t.Errorf("Ping Handler Status Code is NOT 200; got %v", w.Code)

    }


    if w.Body.String() != "Pong" {

        t.Errorf("Ping Handler Response Body is NOT Pong; got %v", w.Body.String())

    }

}


// This fails as it is the same setup as the passing success case

func Test500PingHandler(t *testing.T) {

    req, _ := http.NewRequest("GET", "/ping", nil)

    w := httptest.NewRecorder()


    PingHandler(w, req)


    if w.Code != http.StatusInternalServerError {

        t.Errorf("Ping Handler Status Code is NOT 500; got %v", w.Code)

    }


    if w.Body.String() != "Internal Server Error" {

        t.Errorf("Ping Handler Response Body is NOT Internal Server Error; got %v", w.Body.String())

    }

}


func BenchmarkPingHandler(b *testing.B) {

    for i := 0; i < b.N; i++ {

        req, _ := http.NewRequest("GET", "/ping", nil)

        w := httptest.NewRecorder()


        PingHandler(w, req)

    }

}

ping_handler.go


package main


import (

    "fmt"

    "net/http"

)


func PingHandler(w http.ResponseWriter, r *http.Request) {

    err := writeDefaultHeaders(w, "text")

    if err != nil {

        handleException(w, err)

        return

    }


    fmt.Fprintf(w, "Pong")

}

在这种情况下,我如何测试 json.Marshal 返回错误?


泛舟湖上清波郎朗
浏览 196回答 3
3回答

慕村225694

除非我遗漏了一些东西,否则获取错误的方法是删除硬编码"text"并将您传递的任何内容作为contentType请求中的内容。将其从请求中解析出来,然后将其传递给 writeDefaultHeaders。传递 case 是"text"or "json",其他一切都应该给你你的错误,假设handleException按预期工作(你没有显示它)示例(当然您不希望“Content-Type”标题看起来像这样)package mainimport (&nbsp; &nbsp; "net/http"&nbsp; &nbsp; "net/http/httptest"&nbsp; &nbsp; "testing")func Test200PingHandler(t *testing.T) {&nbsp; &nbsp; req, _ := http.NewRequest("GET", "/ping", nil)&nbsp; &nbsp; req.Header().Set("Content-Type", "text")&nbsp; &nbsp; //req.Header().Set("Content-Type", "json")&nbsp; &nbsp; w := httptest.NewRecorder()&nbsp; &nbsp; PingHandler(w, req)&nbsp; &nbsp; if w.Code != http.StatusOK {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("Ping Handler Status Code is NOT 200; got %v", w.Code)&nbsp; &nbsp; }&nbsp; &nbsp; if w.Body.String() != "Pong" {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("Ping Handler Response Body is NOT Pong; got %v", w.Body.String())&nbsp; &nbsp; }}// This fails as it is the same setup as the passing success casefunc Test500PingHandler(t *testing.T) {&nbsp; &nbsp; req, _ := http.NewRequest("GET", "/ping", nil)&nbsp; &nbsp; req.Header().Set("Content-Type", "fail")&nbsp; &nbsp; w := httptest.NewRecorder()&nbsp; &nbsp; PingHandler(w, req)&nbsp; &nbsp; if w.Code != http.StatusInternalServerError {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("Ping Handler Status Code is NOT 500; got %v", w.Code)&nbsp; &nbsp; }&nbsp; &nbsp; if w.Body.String() != "Internal Server Error" {&nbsp; &nbsp; &nbsp; &nbsp; t.Errorf("Ping Handler Response Body is NOT Internal Server Error; got %v", w.Body.String())&nbsp; &nbsp; }}主要的package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "net/http")func PingHandler(w http.ResponseWriter, r *http.Request) {&nbsp; &nbsp; err := writeDefaultHeaders(w, req.Header().Get("Content-Type"))&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; handleException(w, err)&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Fprintf(w, "Pong")}func writeDefaultHeaders(w http.ResponseWriter, contentType string) error {&nbsp; &nbsp; w.Header().Set("X-Frame-Options", "DENY")&nbsp; &nbsp; w.Header().Set("X-Content-Type-Options", "nosniff")&nbsp; &nbsp; w.Header().Set("X-XSS-Protection", "1;mode=block")&nbsp; &nbsp; switch contentType {&nbsp; &nbsp; case "text":&nbsp; &nbsp; &nbsp; &nbsp; w.Header().Set("Content-Type", "text/plain; charset=utf-8")&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; case "json":&nbsp; &nbsp; &nbsp; &nbsp; w.Header().Set("Content-Type", "application/json; charset=UTF-8")&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; return errors.New("Attempting to render an unknown content type")&nbsp; &nbsp; }}

杨魅力

正如你所写的,这段代码永远不会在 PingHandler 中到达:if err != nil {&nbsp; &nbsp; handleException(w, err)&nbsp; &nbsp; return}因为你唯一返回错误的地方是 writeDefaultHeaders 传递的不是文本或 json 的东西,并且在 PingHandler 中你硬编码“文本”,所以 ping 处理程序永远不会调用 handleException,并且错误处理是多余的。在 writeDefaultHeaders 中没有其他地方可能会返回错误。如果您想测试 handleException,要查看它正确返回 500 错误(这是您在 Test500PingHandler 中断言/测试的内容),只需在测试文件中构造一个 PingHandlerFail 函数,该函数设置不正确的 responseType 并使用它 - 没有其他触发错误代码的方法。func PingHandlerFail(w http.ResponseWriter, r *http.Request) {&nbsp; &nbsp; err := writeDefaultHeaders(w, "foo")&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; handleException(w, err)&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Fprintf(w, "Pong")}或者,更改 PingHandler 以根据某些请求条件设置 contentType,例如请求是否以 .json 结尾(您可能需要这样做以提供 json 或文本),以便您可以以某种方式触发错误 - 目前由于 PingHandler 除了文本之外从不提供任何内容,因此错误代码是多余的并且结果无法测试。

12345678_0001

在代码中模拟这个调用/点击这个错误分支的惯用方法是什么?通常对于测试,您希望使用公共接口并为您的代码提供实现 ( NewMyThing(hw HeaderWriter)) 或使用其他一些机制(例如DefaultHeaderWriter您可以在测试中换出的机制)。由于此代码是私有的,因此您可以只使用一个变量:var writeDefaultHeaders = func(w http.ResponseWriter, contentType string) error {&nbsp; &nbsp; w.Header().Set("X-Frame-Options", "DENY")&nbsp; &nbsp; w.Header().Set("X-Content-Type-Options", "nosniff")&nbsp; &nbsp; w.Header().Set("X-XSS-Protection", "1;mode=block")&nbsp; &nbsp; switch contentType {&nbsp; &nbsp; case "text":&nbsp; &nbsp; &nbsp; &nbsp; w.Header().Set("Content-Type", "text/plain; charset=utf-8")&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; case "json":&nbsp; &nbsp; &nbsp; &nbsp; w.Header().Set("Content-Type", "application/json; charset=UTF-8")&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; default:&nbsp; &nbsp; &nbsp; &nbsp; return errors.New("Attempting to render an unknown content type")&nbsp; &nbsp; }}func PingHandler(w http.ResponseWriter, r *http.Request) {&nbsp; &nbsp; err := writeDefaultHeaders(w, "text")&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; handleException(w, err)&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; }&nbsp; &nbsp; fmt.Fprintf(w, "Pong")}然后在你的测试中换掉它:func Test500PingHandler(t *testing.T) {&nbsp; &nbsp; writeDefaultHeaders = headerWriterFunc(func(w http.ResponseWriter, contentType string) error {&nbsp; &nbsp; &nbsp; &nbsp; return fmt.Errorf("ERROR")&nbsp; &nbsp; })&nbsp; &nbsp; // ...}您可能想在完成后将其设置回原处。在我看来,像这样换出一个单一的功能并不是好的测试实践。测试应该针对公共 API,这样您就可以修改代码,而不必在每次进行更改时都重新编写测试。接口示例:type Marshaler interface {&nbsp; &nbsp; Marshal(v interface{}) ([]byte, error)}type jsonMarshaler struct{}func (_ *jsonMarshaler) Marshal(v interface{}) ([]byte, error) {&nbsp; &nbsp; return json.Marshal(v)}var marshaler Marshaler = (*jsonMarshaler)(nil)进而:json_response, err := marshaler.Marshal(response)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go