# 测试指南 ## 测试类型 SPIRE 包含多种类型的测试: - **单元测试**: 测试单个函数和组件 - **集成测试**: 测试组件间交互 - **端到端测试**: 测试完整工作流程 ## 运行测试 ### 所有测试 ```bash # 运行所有单元测试 make test # 带 race 检测 make race-test ``` ### 特定包 ```bash # 测试特定包 go test ./pkg/server/... # 带详细输出 go test -v ./pkg/server/... # 测试特定函数 go test -v -run TestFunctionName ./pkg/server/... ``` ### 集成测试 ```bash # 运行集成测试 make integration # 特定套件 cd test/integration ./test.sh suites/basic ``` ## 测试覆盖率 ### 生成覆盖率报告 ```bash # 运行测试并生成覆盖率 go test -coverprofile=coverage.out ./... # 查看覆盖率 go tool cover -func=coverage.out # HTML 报告 go tool cover -html=coverage.out -o coverage.html ``` ### 包级覆盖率 ```bash go test -cover ./pkg/server/... ``` ## 编写测试 ### 单元测试示例 ```go 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") } ``` ### 表驱动测试 ```go 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 //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) } ``` ### 使用测试夹具 ```go 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 特定的测试工具: ```go 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 实现: ```go 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 提供可控的时钟: ```go 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 # 主测试脚本 ``` ### 运行集成测试 ```bash # 所有套件 ./test/integration/test.sh # 特定套件 ./test/integration/test.sh suites/basic # 带调试 DEBUG=1 ./test/integration/test.sh suites/basic ``` ### 编写集成测试 ```bash #!/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" ``` ## 调试测试 ### 详细输出 ```bash go test -v -count=1 ./pkg/server/... ``` ### 特定测试 ```bash go test -v -run "TestMyFunction/specific_case" ./pkg/... ``` ### 跳过缓存 ```bash go test -count=1 ./... ``` ### 超时 ```bash go test -timeout 10m ./... ``` ## CI/CD 测试 ### GitHub Actions 项目使用 GitHub Actions 运行测试: ```yaml # .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 ```bash # 运行与 CI 相同的测试 make ci ``` ## 最佳实践 :::{admonition} 测试建议 :class: tip 1. **表驱动测试**: 使用表驱动测试覆盖多种情况 2. **并行测试**: 使用 `t.Parallel()` 加速测试 3. **测试隔离**: 确保测试不依赖外部状态 4. **有意义的名称**: 测试名称应描述测试内容 5. **覆盖边界情况**: 测试空值、错误路径等 6. **保持简短**: 每个测试聚焦单一行为 :::