你的测试代码覆盖率之所以为0,是因为中间件中的日志记录逻辑实际上没有被执行。问题在于timingHandler函数中创建的上下文没有被正确传递。r.WithContext()返回的是一个新的请求副本,但你没有使用它,所以原始请求的上下文并没有被修改。
以下是修复后的测试代码:
func TestTiming(t *testing.T) {
// 创建一个模拟的处理器,它会设置上下文值
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 创建带值的上下文并应用到请求
ctx := context.WithValue(r.Context(), "something", "hello")
r = r.WithContext(ctx)
w.WriteHeader(http.StatusOK)
})
// 创建测试请求和响应记录器
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
res := httptest.NewRecorder()
// 应用中间件
handler := Timing(nextHandler)
// 执行处理
handler.ServeHTTP(res, req)
// 验证状态码
if res.Code != http.StatusOK {
t.Errorf("expected status OK, got %v", res.Code)
}
}
如果你想要更完整的测试,包括验证日志是否被记录,可以使用httptest的ResponseRecorder并模拟日志记录器:
func TestTiming_WithLogging(t *testing.T) {
// 捕获日志输出
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
defer log.SetOutput(os.Stderr)
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "something", "hello")
r = r.WithContext(ctx)
w.WriteHeader(http.StatusOK)
})
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
res := httptest.NewRecorder()
handler := Timing(nextHandler)
handler.ServeHTTP(res, req)
// 验证日志是否包含时间信息
logStr := logOutput.String()
if !strings.Contains(logStr, "time=") {
t.Error("expected timing log output")
}
}
对于中间件测试的最佳实践,我建议:
- 测试中间件的核心逻辑:确保上下文值存在时日志被记录
- 测试中间件的传递性:确保下一个处理器被正确调用
- 使用表格驱动测试:测试不同的场景
func TestTiming_TableDriven(t *testing.T) {
tests := []struct {
name string
contextValue interface{}
expectLog bool
expectedStatus int
}{
{
name: "with context value",
contextValue: "test-id",
expectLog: true,
expectedStatus: http.StatusOK,
},
{
name: "without context value",
contextValue: nil,
expectLog: false,
expectedStatus: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var logOutput bytes.Buffer
log.SetOutput(&logOutput)
defer log.SetOutput(os.Stderr)
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tt.contextValue != nil {
ctx := context.WithValue(r.Context(), "something", tt.contextValue)
r = r.WithContext(ctx)
}
w.WriteHeader(tt.expectedStatus)
})
req := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
res := httptest.NewRecorder()
handler := Timing(nextHandler)
handler.ServeHTTP(res, req)
// 验证状态码
if res.Code != tt.expectedStatus {
t.Errorf("expected status %v, got %v", tt.expectedStatus, res.Code)
}
// 验证日志
hasLog := strings.Contains(logOutput.String(), "time=")
if tt.expectLog && !hasLog {
t.Error("expected log output but got none")
}
if !tt.expectLog && hasLog {
t.Error("expected no log output but got some")
}
})
}
}
这些修改应该能解决你的代码覆盖率问题,并提供更全面的中间件测试。