Golang中测试HTTP处理程序:使用服务器还是不使用服务器?
Golang中测试HTTP处理程序:使用服务器还是不使用服务器? 大家好,
我对测试 HTTP 处理程序感到困惑。我见过两种方法,想了解每种方法的优缺点。
例如,有以下自定义服务器:
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"time"
)
const maxTimeout = 5 * time.Second
var server = &http.Server{}
func main() {
server := setUpServer()
fmt.Println("Server running...")
err := server.ListenAndServe()
if err != nil {
log.Panic(err)
}
}
func setUpServer() *http.Server {
mux := http.NewServeMux()
mux.Handle("/slow", http.TimeoutHandler(http.HandlerFunc(slowHandler), maxTimeout, "timeout"))
server.Addr = ":4040"
server.Handler = mux
return server
}
func slowHandler(w http.ResponseWriter, r *http.Request) {
data := slowOperation(r.Context())
io.WriteString(w, data+"\n")
}
func slowOperation(ctx context.Context) string {
fmt.Println("Working on it...")
select {
case <-ctx.Done():
fmt.Println("Operation timed out.")
return ""
case <-time.After(10 * time.Second):
return "data"
}
}
可以像这样使用自定义服务器测试处理程序:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServer(t *testing.T) {
t.Run("slowHandler should return 503 if request timeouts", func(t *testing.T) {
server := setUpServer()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
server.Handler.ServeHTTP(w, r) // 使用自定义服务器
expected := http.StatusServiceUnavailable
got := w.Result().StatusCode
if got != expected {
t.Errorf("expected request to cause %v, got %v", expected, got)
}
})
}
但也可以像这样测试:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServer(t *testing.T) {
t.Run("slowHandler should return 503 if request timeouts", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
slowHandler(w, r) // 直接使用处理程序
expected := http.StatusServiceUnavailable
got := w.Result().StatusCode
if got != expected {
t.Errorf("expected request to cause %v, got %v", expected, got)
}
})
}
在第一个测试中,我们会得到:
Working on it...
Operation timed out.
PASS
ok test 5.451s
而在第二个测试中:
Working on it...
--- FAIL: TestServer (0.00s)
--- FAIL: TestServer/slowHandler_should_return_503_if_request_timeouts (10.01s)
main_test.go:39: expected request to cause 503, got 200
FAIL
exit status 1
FAIL test 10.230s
因此,我们有第一个测试,它也测试了服务器返回正确输出的实现;而第二个测试纯粹测试处理程序失败,因为它不知道超时设置。此外,使用完全不同的请求(如 httptest.NewRequest(http.MethodDelete, "/", nil))也会产生相同的结果。
处理程序测试应该了解服务器吗?如果使用自定义服务器进行测试,会有什么缺点吗?
更多关于Golang中测试HTTP处理程序:使用服务器还是不使用服务器?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢,我会看看的!
更多关于Golang中测试HTTP处理程序:使用服务器还是不使用服务器?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
谢谢,但 http.Server 的超时设置并不适用于处理器:https://stackoverflow.com/a/51259258/8966651 此外,为了加速使用自定义服务器的测试,如果在每个测试中都实例化服务器,可以使用 t.Parallel()。
我的问题更多是关于使用自定义服务器进行测试是否是一种最佳实践。
在我看来,如果你想这样做,httptest.NewServer 可能是最佳选择。但这并不意味着你的设置是错误的,只是为了简洁起见。最重要的是,只要你不为代码库的一致性而同时使用两者,那就没问题。
为了提供更多信息,我找到了类似这样的内容。它主要讨论了一些关于模拟服务器的内容,但没有明确说明其优缺点。
Go 语言中 httptest 包的强大之处
Go 语言测试之所以友好,原因之一在于核心团队已经提供了有用的测试包作为标准库的一部分,你可以直接使用,就像他们测试依赖这些包的代码一样。本文解释了如何…
在Golang中测试HTTP处理程序时,是否使用服务器取决于测试目标。以下是两种方法的详细对比:
1. 直接测试处理程序(不使用服务器)
这种方法直接调用处理函数,适合单元测试:
func TestSlowHandlerDirect(t *testing.T) {
t.Run("handler returns 200 without timeout", func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
// 创建带取消的context来模拟超时
ctx, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)
defer cancel()
r = r.WithContext(ctx)
slowHandler(w, r)
if w.Code != http.StatusOK {
t.Errorf("expected 200, got %d", w.Code)
}
})
}
优点:
- 测试隔离性好,只测试处理程序逻辑
- 执行速度快
- 可以精确控制输入参数
缺点:
- 无法测试中间件(如TimeoutHandler)
- 无法测试路由配置
- 无法测试服务器级别的行为
2. 通过服务器测试(使用httptest.Server或自定义服务器)
使用httptest.Server:
func TestSlowHandlerWithTestServer(t *testing.T) {
// 创建测试服务器
mux := http.NewServeMux()
mux.Handle("/slow", http.TimeoutHandler(
http.HandlerFunc(slowHandler),
maxTimeout,
"timeout",
))
ts := httptest.NewServer(mux)
defer ts.Close()
t.Run("request times out with 503", func(t *testing.T) {
client := ts.Client()
client.Timeout = maxTimeout
resp, err := client.Get(ts.URL + "/slow")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", resp.StatusCode)
}
})
}
使用自定义服务器(如你的示例):
func TestSlowHandlerWithCustomServer(t *testing.T) {
t.Run("test timeout through server handler chain", func(t *testing.T) {
server := setUpServer()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
// 通过完整的处理链执行
server.Handler.ServeHTTP(w, r)
if w.Code != http.StatusServiceUnavailable {
t.Errorf("expected 503, got %d", w.Code)
}
})
t.Run("test other endpoints", func(t *testing.T) {
server := setUpServer()
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/health", nil)
// 测试其他路由
server.Handler.ServeHTTP(w, r)
// 验证响应
})
}
优点:
- 测试完整的请求处理链(中间件、路由等)
- 更接近生产环境行为
- 可以测试服务器配置(如超时设置)
缺点:
- 执行速度较慢
- 测试可能依赖外部配置
- 测试失败时难以定位问题根源
3. 混合测试策略示例
在实际项目中,通常结合两种方法:
// 单元测试:测试处理程序核心逻辑
func TestSlowHandlerLogic(t *testing.T) {
tests := []struct {
name string
ctx context.Context
wantCode int
}{
{
name: "normal context",
ctx: context.Background(),
wantCode: http.StatusOK,
},
{
name: "cancelled context",
ctx: func() context.Context {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return ctx
}(),
wantCode: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/slow", nil)
r = r.WithContext(tt.ctx)
slowHandler(w, r)
if w.Code != tt.wantCode {
t.Errorf("got status %d, want %d", w.Code, tt.wantCode)
}
})
}
}
// 集成测试:测试完整服务器行为
func TestServerIntegration(t *testing.T) {
server := setUpServer()
tests := []struct {
name string
path string
method string
wantCode int
}{
{
name: "slow endpoint with timeout",
path: "/slow",
method: http.MethodGet,
wantCode: http.StatusServiceUnavailable,
},
{
name: "non-existent endpoint",
path: "/not-found",
method: http.MethodGet,
wantCode: http.StatusNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
r := httptest.NewRequest(tt.method, tt.path, nil)
server.Handler.ServeHTTP(w, r)
if w.Code != tt.wantCode {
t.Errorf("%s: got status %d, want %d", tt.name, w.Code, tt.wantCode)
}
})
}
}
结论
在你的具体案例中,第二个测试失败是因为直接调用slowHandler绕过了TimeoutHandler中间件。处理程序本身没有设置503状态码,这个状态码是由超时中间件设置的。
建议:
- 对处理程序的核心业务逻辑进行单元测试(不使用服务器)
- 对包含中间件和路由的完整处理链进行集成测试(使用服务器)
- 使用
httptest.NewServer进行端到端测试,模拟真实HTTP请求
两种方法各有适用场景,通常在实际项目中会同时使用,以确保代码质量和测试覆盖率。


