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

144 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [go, testing, integration-test, project-structure, engineering]
create time: 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() 封装:创建测试库 + 自动清理
```
### 各子目录的职责
```mermaid
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. 测试数据隔离
每个测试用例应使用独立的数据命名空间,避免相互污染:
```go
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 最常见的失败原因:
```go
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 中的分级执行
```yaml
# .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 的代码
## 关联笔记
- [[Go 工程模块化]]