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

时间:2015-09-08 02:56:18

标签: unit-testing testing go tdd

我正在尝试在这个简单的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")
}

func writeDefaultHeaders(w http.ResponseWriter, contentType string) error {
    w.Header().Set("X-Frame-Options", "DENY")
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.Header().Set("X-XSS-Protection", "1;mode=block")

    switch contentType {
    case "text":
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        return nil
    case "json":
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        return nil
    default:
        return errors.New("Attempting to render an unknown content type")
    }
}

修改 另一个例子:

json_response, err := json.Marshal(response)
if err != nil {
    handleException(w, err)
    return
}

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

3 个答案:

答案 0 :(得分:3)

  

在代码中模拟此调用/命中此错误分支的惯用方法是什么?

通常,对于测试,您希望使用公共接口,并为代码提供实现(NewMyThing(hw HeaderWriter))或使用其他一些机制(如DefaultHeaderWriter,您可以在测试中换出)

由于此代码是私有的,您只能使用变量:

var writeDefaultHeaders = func(w http.ResponseWriter, contentType string) error {
    w.Header().Set("X-Frame-Options", "DENY")
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.Header().Set("X-XSS-Protection", "1;mode=block")

    switch contentType {
    case "text":
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        return nil
    case "json":
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        return nil
    default:
        return errors.New("Attempting to render an unknown content type")
    }
}

func PingHandler(w http.ResponseWriter, r *http.Request) {
    err := writeDefaultHeaders(w, "text")
    if err != nil {
        handleException(w, err)
        return
    }

    fmt.Fprintf(w, "Pong")
}

然后在测试中将其换掉:

func Test500PingHandler(t *testing.T) {
    writeDefaultHeaders = headerWriterFunc(func(w http.ResponseWriter, contentType string) error {
        return fmt.Errorf("ERROR")
    })
    // ...
}

您可能希望在完成后将其重新设置。

在我看来,交换这样的单个函数并不是一个好的测试实践。测试应该针对公共API,这样您就可以修改代码,而无需在每次进行一次更改时重写测试。

接口示例:

type Marshaler interface {
    Marshal(v interface{}) ([]byte, error)
}

type jsonMarshaler struct{}

func (_ *jsonMarshaler) Marshal(v interface{}) ([]byte, error) {
    return json.Marshal(v)
}

var marshaler Marshaler = (*jsonMarshaler)(nil)

然后:

json_response, err := marshaler.Marshal(response)

答案 1 :(得分:1)

除非我遗漏某些内容,否则错误的方法是删除硬编码的"text",并将contentType中传递的内容作为请求中的内容。将其解析出请求,然后将其传递给writeDefaultHeaders。传递案例可以是"text""json",其他一切都应该给您错误,假设handleException按预期工作(您还没有显示)

示例(当然,您不希望&#34; Content-Type&#34;标题看起来像这样)

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func Test200PingHandler(t *testing.T) {
    req, _ := http.NewRequest("GET", "/ping", nil)
    req.Header().Set("Content-Type", "text")
    //req.Header().Set("Content-Type", "json")
    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)
    req.Header().Set("Content-Type", "fail")
    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())
    }
}

package main

import (
    "fmt"
    "net/http"
)

func PingHandler(w http.ResponseWriter, r *http.Request) {
    err := writeDefaultHeaders(w, req.Header().Get("Content-Type"))
    if err != nil {
        handleException(w, err)
        return
    }

    fmt.Fprintf(w, "Pong")
}

func writeDefaultHeaders(w http.ResponseWriter, contentType string) error {
    w.Header().Set("X-Frame-Options", "DENY")
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.Header().Set("X-XSS-Protection", "1;mode=block")

    switch contentType {
    case "text":
        w.Header().Set("Content-Type", "text/plain; charset=utf-8")
        return nil
    case "json":
        w.Header().Set("Content-Type", "application/json; charset=UTF-8")
        return nil
    default:
        return errors.New("Attempting to render an unknown content type")
    }
}

答案 2 :(得分:0)

正如您所写,在PingHandler中永远不会达到此代码:

if err != nil {
    handleException(w, err)
    return
}

因为你返回错误的唯一地方是writeDefaultHeaders传递的东西不是text或json,而在PingHandler中你硬编码“text”,所以ping处理程序永远不会调用handleException,错误处理是多余的。在writeDefaultHeaders中没有其他地方可能会返回错误。

如果你想测试handleException,看它正确地返回500错误(这是你在Test500PingHandler中断言/测试的那个),只需在测试文件中构造一个PingHandlerFail函数,该函数设置一个不正确的responseType并使用它 - 那里没有其他方法可以触发您的错误代码。

func PingHandlerFail(w http.ResponseWriter, r *http.Request) {
    err := writeDefaultHeaders(w, "foo")
    if err != nil {
        handleException(w, err)
        return
    }
    fmt.Fprintf(w, "Pong")
}

或者,更改PingHandler以根据某些请求条件设置contentType,例如请求是否以.json结尾(您可能需要这样做才能提供json或文本),以便您可以以某种方式触发错误 - 目前,由于PingHandler从不提供除文本之外的任何内容,因此错误代码是多余的,结果不可测试。