Gin 测试覆盖率分析
测试覆盖率衡量代码被测试覆盖的程度,是评估测试质量的重要指标。
基础覆盖率采集
运行覆盖率测试
Bash
# 基本覆盖率统计
go test -cover ./...
# 输出示例
ok github.com/example/gin-api 0.5s coverage: 75.2%
ok github.com/example/gin-api/handlers 0.3s coverage: 82.1%
# 详细模式
go test -cover -v ./...
生成覆盖率文件
Bash
# 生成覆盖率数据文件
go test -coverprofile=coverage.out ./...
# 查看覆盖率函数
go tool cover -func=coverage.out
# 输出示例
github.com/example/gin-api/handlers/user.go:15: GetUserHandler 85.0%
github.com/example/gin-api/handlers/user.go:25: CreateUserHandler 70.0%
github.com/example/gin-api/middleware/auth.go:10: AuthMiddleware 90.0%
total: (statements) 78.5%
HTML 覆盖率报告
生成可视化报告
Bash
# 生成 HTML 报告
go tool cover -html=coverage.out -o coverage.html
# 直接在浏览器打开
go tool cover -html=coverage.out
HTML 报告说明
Bash
- 绿色:已覆盖的代码行
- 红色:未覆盖的代码行
- 灰色:非可执行代码(注释、声明等)
覆盖率模式
set 模式(默认)
Bash
# 检测代码行是否被执行
go test -covermode=set -coverprofile=coverage.out ./...
count 模式
Bash
# 记录每行执行次数
go test -covermode=count -coverprofile=coverage.out ./...
# 可识别热点代码和测试盲区
atomic 模式
Bash
# 多测试并发时使用,保证计数准确
go test -covermode=atomic -coverprofile=coverage.out ./...
分包覆盖率
按包分析
Bash
# 分析每个包的覆盖率
go test -cover ./handlers ./middleware ./services
# 输出每个包的覆盖率
ok handlers 0.2s coverage: 80.0%
ok middleware 0.1s coverage: 65.0%
ok services 0.3s coverage: 90.0%
合合覆盖率报告
makefile
# 多包合并报告
go test -coverprofile=coverage.out ./handlers ./middleware ./services
# 总覆盖率
go tool cover -func=coverage.out | grep total
覆盖率阈值配置
Makefile 配置
YAML
# Makefile
test:
go test -v ./...
coverage:
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
coverage-html:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
coverage-check:
go test -coverprofile=coverage.out ./...
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $$3}' | sed 's/%//')
if [ "$COVERAGE" -lt 80 ]; then \
echo "Coverage $$COVERAGE% is below threshold 80%"; \
exit 1; \
fi
CI 配置
Go
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.21'
- name: Run tests
run: go test -v ./...
- name: Generate coverage
run: go test -coverprofile=coverage.out ./...
- name: Check coverage
run: |
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo "Coverage: $COVERAGE%"
if [ "$COVERAGE" -lt 80 ]; then
echo "Coverage below threshold"
exit 1
fi
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: coverage.out
Gin Handler 覆盖率
测试场景覆盖
Go
func TestGetUserHandler_Coverage(t *testing.T) {
r := gin.New()
r.GET("/users/:id", GetUserHandler)
tests := []struct {
name string
id string
wantCode int
}{
{"正常用户", "123", 200},
{"用户不存在", "999", 404},
{"无效ID", "abc", 400},
{"空ID", "", 400},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/users/"+tt.id, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, tt.wantCode, w.Code)
})
}
}
中间件覆盖率
Go
func TestAuthMiddleware_Coverage(t *testing.T) {
r := gin.New()
r.Use(AuthMiddleware())
r.GET("/protected", func(c *gin.Context) {
c.String(200, "ok")
})
tests := []struct {
name string
token string
wantCode int
}{
{"有效token", "valid-token", 200},
{"空token", "", 401},
{"无效token", "invalid", 401},
{"过期token", "expired", 401},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/protected", nil)
if tt.token != "" {
req.Header.Set("Authorization", tt.token)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, tt.wantCode, w.Code)
})
}
}
覆盖率提升策略
覆盖所有分支
Go
func ProcessRequest(c *gin.Context) {
status := c.Query("status")
if status == "active" {
c.JSON(200, gin.H{"type": "active"})
} else if status == "inactive" {
c.JSON(200, gin.H{"type": "inactive"})
} else {
c.JSON(400, gin.H{"error": "invalid status"})
}
}
func TestProcessRequest_AllBranches(t *testing.T) {
r := gin.New()
r.GET("/process", ProcessRequest)
// 测试所有分支
tests := []struct {
status string
wantCode int
}{
{"active", 200},
{"inactive", 200},
{"invalid", 400},
{"", 400},
}
for _, tt := range tests {
req := httptest.NewRequest("GET", "/process?status="+tt.status, nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, tt.wantCode, w.Code)
}
}
异常路径覆盖
Bash
func TestCreateUser_AllErrors(t *testing.T) {
r := gin.New()
r.POST("/users", CreateUserHandler)
// 正常情况
body := `{"name":"Valid","email":"valid@example.com"}`
req := httptest.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 201, w.Code)
// 名字为空
body = `{"name":"","email":"valid@example.com"}`
req = httptest.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 400, w.Code)
// 邮箱格式错误
body = `{"name":"Valid","email":"invalid"}`
req = httptest.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 400, w.Code)
// JSON 格式错误
body = `{"name":}`
req = httptest.NewRequest("POST", "/users", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, 400, w.Code)
}
覆盖率报告解读
text
github.com/example/gin-api/handlers/user.go:15: GetUserHandler 85.0%
| 字段 | 说明 |
|---|---|
| user.go:15 | 文件名和起始行号 |
| GetUserHandler | 函数名 |
| 85.0% | 该函数覆盖率 |
覆盖率排除
text
# 排除特定文件
go test -coverprofile=coverage.out $(go list ./... | grep -v 'mock')
# 代码中排除(不推荐)
//go:nosplit // 不计入覆盖率统计
覆盖率目标
| 项目类型 | 建议覆盖率 |
|---|---|
| 核心业务逻辑 | > 80% |
| 中间件 | > 70% |
| 边缘功能 | > 50% |
| 工具函数 | > 90% |
注意:覆盖率不是唯一指标,测试质量比覆盖率更重要。
要点总结
- 基本命令:
go test -cover ./... - 生成报告:
go tool cover -html=coverage.out - 覆盖率模式:set/count/atomic
- 分支覆盖:测试所有 if/else 分支
- 异常路径:覆盖错误处理代码
- CI集成:设置覆盖率阈值检查
📝 发现内容有误?点击此处直接编辑