测试指南

测试类型

SPIRE 包含多种类型的测试:

  • 单元测试: 测试单个函数和组件

  • 集成测试: 测试组件间交互

  • 端到端测试: 测试完整工作流程

运行测试

所有测试

# 运行所有单元测试
make test

# 带 race 检测
make race-test

特定包

# 测试特定包
go test ./pkg/server/...

# 带详细输出
go test -v ./pkg/server/...

# 测试特定函数
go test -v -run TestFunctionName ./pkg/server/...

集成测试

# 运行集成测试
make integration

# 特定套件
cd test/integration
./test.sh suites/basic

测试覆盖率

生成覆盖率报告

# 运行测试并生成覆盖率
go test -coverprofile=coverage.out ./...

# 查看覆盖率
go tool cover -func=coverage.out

# HTML 报告
go tool cover -html=coverage.out -o coverage.html

包级覆盖率

go test -cover ./pkg/server/...

编写测试

单元测试示例

package mypackage

import (
    "testing"
    
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestMyFunction(t *testing.T) {
    // 准备
    input := "test"
    expected := "TEST"
    
    // 执行
    result := MyFunction(input)
    
    // 验证
    assert.Equal(t, expected, result)
}

func TestMyFunctionError(t *testing.T) {
    // 测试错误情况
    _, err := MyFunction("")
    require.Error(t, err)
    assert.Contains(t, err.Error(), "empty input")
}

表驱动测试

func TestMyFunction(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
        wantErr  bool
    }{
        {
            name:     "basic",
            input:    "test",
            expected: "TEST",
        },
        {
            name:    "empty",
            input:   "",
            wantErr: true,
        },
        {
            name:     "with spaces",
            input:    "hello world",
            expected: "HELLO WORLD",
        },
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result, err := MyFunction(tt.input)
            if tt.wantErr {
                require.Error(t, err)
                return
            }
            require.NoError(t, err)
            assert.Equal(t, tt.expected, result)
        })
    }
}

使用 Mocks

//go:generate mockgen -destination=mock_store.go -package=mypackage . Store

type Store interface {
    Get(key string) (string, error)
    Set(key, value string) error
}

func TestWithMock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    
    mockStore := NewMockStore(ctrl)
    
    // 设置期望
    mockStore.EXPECT().
        Get("key1").
        Return("value1", nil).
        Times(1)
    
    // 测试
    result, err := myFunctionUsingStore(mockStore, "key1")
    require.NoError(t, err)
    assert.Equal(t, "value1", result)
}

使用测试夹具

import (
    "testing"
    
    "github.com/spiffe/spire/test/spiretest"
)

func TestWithFixtures(t *testing.T) {
    // 使用测试 CA
    ca := spiretest.NewCA(t)
    
    // 生成 SVID
    svid := ca.CreateX509SVID("spiffe://example.org/test")
    
    // 测试...
}

测试工具

test/spiretest

提供 SPIRE 特定的测试工具:

import "github.com/spiffe/spire/test/spiretest"

func TestExample(t *testing.T) {
    // 临时目录
    dir := spiretest.TempDir(t)
    
    // 测试 gRPC 服务
    spiretest.ServeGRPC(t, server, func(conn *grpc.ClientConn) {
        // 测试...
    })
}

test/fakes

提供 fake 实现:

import "github.com/spiffe/spire/test/fakes/fakeagentstore"

func TestWithFake(t *testing.T) {
    store := fakeagentstore.New()
    store.SetAgentInfo(&common.AttestedNode{
        SpiffeId: "spiffe://example.org/agent",
    })
    
    // 测试...
}

test/clock

提供可控的时钟:

import "github.com/spiffe/spire/test/clock"

func TestWithClock(t *testing.T) {
    clk := clock.NewMock(t)
    
    // 设置时间
    clk.Set(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
    
    // 时间前进
    clk.Add(time.Hour)
    
    // 测试...
}

集成测试

结构

test/integration/
├── setup/          # 测试设置脚本
├── suites/         # 测试套件
│   ├── basic/
│   ├── federation/
│   └── ...
└── test.sh         # 主测试脚本

运行集成测试

# 所有套件
./test/integration/test.sh

# 特定套件
./test/integration/test.sh suites/basic

# 带调试
DEBUG=1 ./test/integration/test.sh suites/basic

编写集成测试

#!/bin/bash

# test/integration/suites/mytest/test.sh

source "${TESTDIR}/common"

# 启动 Server
start_server server.conf

# 启动 Agent
start_agent agent.conf

# 创建注册条目
create_registration_entry \
    -spiffeID spiffe://example.org/test \
    -selector unix:uid:1000

# 验证
check_svid "spiffe://example.org/test"

调试测试

详细输出

go test -v -count=1 ./pkg/server/...

特定测试

go test -v -run "TestMyFunction/specific_case" ./pkg/...

跳过缓存

go test -count=1 ./...

超时

go test -timeout 10m ./...

CI/CD 测试

GitHub Actions

项目使用 GitHub Actions 运行测试:

# .github/workflows/pr_build.yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v4
        with:
          go-version-file: .go-version
      - run: make test

本地模拟 CI

# 运行与 CI 相同的测试
make ci

最佳实践

测试建议

  1. 表驱动测试: 使用表驱动测试覆盖多种情况

  2. 并行测试: 使用 t.Parallel() 加速测试

  3. 测试隔离: 确保测试不依赖外部状态

  4. 有意义的名称: 测试名称应描述测试内容

  5. 覆盖边界情况: 测试空值、错误路径等

  6. 保持简短: 每个测试聚焦单一行为