Skip to content

第27章:测试 ​

本章目标 ​

学完本章后,你将能够:

  1. 使用 httptest 为 Gin 接口写单元测试
  2. 测试 GET/POST/PUT/DELETE 接口
  3. 测试中间件
  4. 使用表驱动测试提高测试覆盖率

前置知识 ​

  • 需要先学习:第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) 执行请求
  • 表驱动测试覆盖多种输入场景
  • 中间件也可以像普通接口一样测试

练习题 ​

  1. 为 /ping 接口写一个完整测试。
  2. 为 POST /users 接口写测试,包括正常创建和参数缺失的用例。
  3. 用表驱动测试覆盖一个接口的多种输入。
  4. 为 JWT 认证中间件写测试(有 Token/无 Token/过期 Token)。
  5. (思考题)单元测试和集成测试的边界在哪里?什么时候用 httptest,什么时候用真实请求?

把零散知识,整理成可以反复走的路。