This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/GO/工程模块化/测试目录结构.md
T

5.1 KiB
Raw Blame History

tags, create time
tags create time
go
testing
integration-test
project-structure
engineering
2026-04-29 15:45

集成测试目录结构

概述

梳理 Go 项目中测试目录的组织方式,聚焦集成测试的定位、目录划分和最佳实践。明确单测与集成测试的边界,帮助团队在工程规模增长时保持测试可维护性。

为什么需要独立测试目录?

[!question] 思考:单测不够吗?为什么还要单独建 tests/?

Go 的社区惯例是单测和源码放一起(xxx_test.go),这已经覆盖了绝大多数场景。但当测试需要真实基础设施(数据库、Redis、HTTP Server)时,就需要引入独立的集成测试目录。

维度 包内单测 tests/ 集成测试
视角 白盒——能看到 unexported 黑盒——只调公开 API
速度 快(纯内存操作) 慢(需启动依赖)
职责 验证函数行为 验证组件协作
CI 触发 每次 commit 必跑 可单独控制,按需跑

tests/ 的典型结构

tests/
├── integration/          # 集成测试:组件之间正确协作
│   ├── user_test.go      # 用户注册全流程(handler → service → repo → DB)
│   └── order_test.go     # 下单流程(含事务、并发安全校验)
├── e2e/                  # 端到端测试:整个服务对外可见的行为
│   └── api_smoke_test.go # 启动 server,发 HTTP 请求做冒烟测试
├── fixtures/             # 测试数据fixtures
│   ├── users.json        # 预置数据
│   └── orders.json
└── testutil/             # 集成测试专用辅助包
    └── db.go             # TestDB() 封装:创建测试库 + 自动清理

各子目录的职责

flowchart TD
    A["tests/"] --> B["integration/ — 集成测试<br/>组件协作 + 真实依赖"]
    A --> C["e2e/ — 端到端测试<br/>完整服务 + HTTP 调用"]
    A --> D["fixtures/ — 测试数据"]
    A --> E["testutil/ — 基础设施辅助"]

    style B fill:#bfb,stroke:#333,stroke-width:3px
    style C fill:#fbf,stroke:#333
    style D fill:#fff,stroke:#333
    style E fill:#fff,stroke:#333

[!tip] 何时需要哪个?

  • 只有 unit test:项目初期、CRUD 为主 → 不需要 tests/,全放包内
  • 加了外部依赖后:有数据库/缓存/消息队列 → 引入 tests/integration/
  • 上线前质量保障:微服务、对外 API → 增加 tests/e2e/

集成测试的最佳实践

1. 测试数据隔离

每个测试用例应使用独立的数据命名空间,避免相互污染:

func TestCreateOrder(t *testing.T) {
    // 为当前测试准备干净的数据库状态
    db := testutil.NewTestDB(t)
    defer db.Cleanup() // 事务回滚或 truncate

    svc := service.NewOrderService(db.Conn())
    res, err := svc.Create(ctx, payload)
    assert.NoError(t, err)

    // 断言基于干净状态的预期结果
}

[!info] 两种隔离策略

策略 做法 适用场景
事务回滚 每件事务包裹在 BEGIN/ROLLBACK 中 PostgreSQL/MySQL,速度快
临时 Schema 每个 test 创建独立 schema 需要测试迁移、DDL 等场景

2. 避免硬编码端口

集成测试需要启动服务,端口冲突是 CI 最常见的失败原因:

func TestAPIIntegration(t *testing.T) {
    // ✅ 动态分配可用端口
    l, err := net.Listen("tcp", "127.0.0.1:0")
    addr := l.Addr().String()
    l.Close()

    srv := &http.Server{Addr: addr, Handler: router}
    // ...
}

[!warning] 常见坑

  • 不要写死 8080——本地开发可能已经在跑别的服务
  • net.Listener 用 "127.0.0.1:0" 让内核分配空闲端口是最稳妥的方式

3. CI 中的分级执行

# .github/workflows/test.yml 示例思路
- run: go test ./...                      # 所有单测(每次 commit)
- run: go test ./tests/integration/...    # 集成测试(push to main 或手动触发)
- run: go test ./tests/e2e/...            # E2E(release 前)

[!summary] 分级执行的理由

集成测试和 E2E 跑起来可能几分钟甚至更久,混在常规 CI 里会拖慢开发节奏。按阶段分级,既能保证质量又不牺牲效率。

与内部包的配合

集成测试虽然放在项目根级的 tests/ 下,但它访问的是你 internal/ 里的代码。这里有个重要细节:

my-project/
├── internal/user/service/
│   └── service.go       # package service — 对外不可见
├── tests/integration/
│   └── user_test.go     # import "my-project/internal/user/service" — 合法!

[!tip] 关键点

  • internal/ 的限制是对其它 module 而言的,同一 module 内的任何位置都可以 import
  • tests/ 和 internal/ 属于同一个 module,所以可以毫无阻碍地访问私有包
  • 这意味着集成测试其实是半白盒的——你能看到 internal 但看不到第三方 module 的代码

关联笔记