第27章:测试
本章目标
学完本章后,你将能够:
- 使用
httptest为 Gin 接口写单元测试 - 测试 GET/POST/PUT/DELETE 接口
- 测试中间件
- 使用表驱动测试提高测试覆盖率
前置知识
- 需要先学习:第03章(路由基础)、Go
testing包基础
27.1 httptest 基础
go
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
gin.SetMode(gin.TestMode) // 测试模式
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"msg": "pong"})
})
return r
}
func TestPing(t *testing.T) {
router := setupRouter()
// 创建测试请求
req, _ := http.NewRequest("GET", "/ping", nil)
// 创建响应记录器
w := httptest.NewRecorder()
// 执行请求
router.ServeHTTP(w, req)
// 断言
if w.Code != http.StatusOK {
t.Errorf("期望状态码 200, 得到 %d", w.Code)
}
expected := `{"msg":"pong"}`
if w.Body.String() != expected {
t.Errorf("期望 %s, 得到 %s", expected, w.Body.String())
}
}27.2 测试 POST 接口(带 JSON Body)
go
func TestCreateUser(t *testing.T) {
router := setupRouter()
body := `{"username":"zhangsan","password":"123456","email":"zs@example.com"}`
req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusCreated {
t.Errorf("期望状态码 201, 得到 %d", w.Code)
}
}27.3 测试中间件
go
func TestAuthMiddleware(t *testing.T) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(JWTAuthMiddleware())
r.GET("/protected", func(c *gin.Context) {
c.String(200, "ok")
})
// 无 Token:期望 401
req, _ := http.NewRequest("GET", "/protected", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != 401 {
t.Errorf("无Token期望 401, 得到 %d", w.Code)
}
// 有 Token:期望 200
token, _ := GenerateToken(1, "test")
req2, _ := http.NewRequest("GET", "/protected", nil)
req2.Header.Set("Authorization", "Bearer "+token)
w2 := httptest.NewRecorder()
r.ServeHTTP(w2, req2)
if w2.Code != 200 {
t.Errorf("有Token期望 200, 得到 %d", w2.Code)
}
}27.4 表驱动测试
go
func TestGetUser(t *testing.T) {
router := setupRouter()
tests := []struct {
name string
userID string
wantStatus int
}{
{"存在的用户", "1", 200},
{"不存在的用户", "999", 404},
{"无效的用户ID", "abc", 400},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/users/"+tt.userID, nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != tt.wantStatus {
t.Errorf("期望 %d, 得到 %d", tt.wantStatus, w.Code)
}
})
}
}本章小结
gin.SetMode(gin.TestMode)关闭调试日志httptest.NewRecorder()记录响应router.ServeHTTP(w, req)执行请求- 表驱动测试覆盖多种输入场景
- 中间件也可以像普通接口一样测试
练习题
- 为
/ping接口写一个完整测试。 - 为 POST
/users接口写测试,包括正常创建和参数缺失的用例。 - 用表驱动测试覆盖一个接口的多种输入。
- 为 JWT 认证中间件写测试(有 Token/无 Token/过期 Token)。
- (思考题)单元测试和集成测试的边界在哪里?什么时候用 httptest,什么时候用真实请求?