[英]Go simple http handler testing all paths
我试图在这个简单的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返回错误?
在代码中模拟此调用/命中此错误分支的惯用方式是什么?
通常,对于测试,您想要使用公共接口并为代码提供实现( 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)
除非我丢失了某些东西,否则获取错误的方法是删除硬编码的"text"
并使您作为contentType
传递的contentType
成为请求中的内容。 从请求中解析它,然后将其传递给writeDefaultHeaders。 传递的大小写是"text"
或"json"
,假设handleException
可以按预期工作(您未显示),其他所有内容都应该给您带来错误
示例(当然,您不希望“ Content-Type”标头看起来像这样)
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")
}
}
如您所写,在PingHandler中永远不会到达以下代码:
if err != nil {
handleException(w, err)
return
}
因为返回错误的唯一地方是在传递writeDefaultHeaders而不是传递文本或json之外的其他内容时,并且在PingHandler中对“文本”进行硬编码,所以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仅提供文本服务,因此错误代码是多余的,并且结果不可测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.