测试覆盖率

测试覆盖率衡量测试代码对业务代码的覆盖程度,是评估测试质量的重要指标。

生成覆盖率报告

使用 Go 的覆盖率工具:

go test -cover ./...

输出类似:

ok      myapp/handlers    0.123s    coverage: 75.0% of statements
ok      myapp/services    0.456s    coverage: 60.0% of statements

详细覆盖率报告

生成覆盖率文件:

go test -coverprofile=coverage.out ./...

查看函数级别覆盖率:

go tool cover -func=coverage.out

输出:

main.go:15:    main          100.0%
handlers/user.go:10:    GetUser     80.0%
handlers/user.go:25:    CreateUser  70.0%
total:          75.0%

HTML 覆盖率报告

生成可视化的 HTML 报告:

go tool cover -html=coverage.out -o coverage.html

在浏览器中打开,可以看到哪些代码被覆盖,哪些没有。

按包统计

go test -coverpkg=./... -coverprofile=coverage.out ./...
go tool cover -func=coverage.out

Makefile 集成

.PHONY: test coverage

test:
    go test -v ./...

coverage:
    go test -coverprofile=coverage.out ./...
    go tool cover -html=coverage.out -o coverage.html
    go tool cover -func=coverage.out

CI/CD 集成

GitHub Actions 示例:

name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-go@v4
        with:
          go-version: '1.21'
      
      - name: Run tests
        run: go test -v -coverprofile=coverage.out ./...
      
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: coverage.out

覆盖率目标

设置覆盖率门槛:

#!/bin/bash

coverage=$(go test -cover ./... 2>&1 | grep total | awk '{print $4}' | sed 's/%//')

if [ "$coverage" -lt 70 ]; then
    echo "Coverage $coverage% is below threshold 70%"
    exit 1
fi

echo "Coverage $coverage% passed"

提高覆盖率的技巧

测试边界情况

func TestGetUser_EdgeCases(t *testing.T) {
    tests := []struct {
        name       string
        userID     string
        wantStatus int
    }{
        {"正常ID", "1", 200},
        {"空ID", "", 400},
        {"非数字ID", "abc", 400},
        {"负数ID", "-1", 404},
        {"超大ID", "999999999", 404},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            // 测试代码
        })
    }
}

测试错误路径

func TestCreateUser_ErrorPaths(t *testing.T) {
    t.Run("无效JSON", func(t *testing.T) {
        // 测试无效 JSON
    })
    
    t.Run("验证失败", func(t *testing.T) {
        // 测试验证失败
    })
    
    t.Run("数据库错误", func(t *testing.T) {
        // 测试数据库错误
    })
}

使用 Mock 隔离

func TestWithMockDB(t *testing.T) {
    mockDB := &MockDB{
        Error: errors.New("connection failed"),
    }
    
    // 测试数据库错误场景
}

覆盖率报告示例

package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
    
    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)

func TestUserHandlers(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    r := setupRouter()
    
    t.Run("获取用户列表", func(t *testing.T) {
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("GET", "/users", nil)
        r.ServeHTTP(w, req)
        
        assert.Equal(t, http.StatusOK, w.Code)
    })
    
    t.Run("创建用户", func(t *testing.T) {
        body := `{"name":"张三","email":"test@example.com"}`
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("POST", "/users", strings.NewReader(body))
        req.Header.Set("Content-Type", "application/json")
        r.ServeHTTP(w, req)
        
        assert.Equal(t, http.StatusCreated, w.Code)
    })
    
    t.Run("获取单个用户", func(t *testing.T) {
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("GET", "/users/1", nil)
        r.ServeHTTP(w, req)
        
        assert.Equal(t, http.StatusOK, w.Code)
    })
    
    t.Run("删除用户", func(t *testing.T) {
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("DELETE", "/users/1", nil)
        r.ServeHTTP(w, req)
        
        assert.Equal(t, http.StatusNoContent, w.Code)
    })
}

小结

测试覆盖率是衡量测试质量的重要指标。使用 Go 内置工具生成覆盖率报告,集成到 CI/CD 中自动检查。覆盖率不是唯一标准,但可以作为参考。重点是测试关键路径和边界情况,而不是追求 100% 覆盖率。