288 lines
6.5 KiB
Markdown
288 lines
6.5 KiB
Markdown
# 测试指南
|
|
|
|
本项目的测试使用 **Convey 框架**,分为三个层次:单元测试、集成测试和端到端测试。
|
|
|
|
## 测试结构
|
|
|
|
```
|
|
tests/
|
|
├── setup_test.go # 测试设置和通用工具
|
|
├── unit/ # 单元测试
|
|
│ ├── config_test.go # 配置测试
|
|
│ └── ... # 其他单元测试
|
|
├── integration/ # 集成测试
|
|
│ ├── database_test.go # 数据库集成测试
|
|
│ └── ... # 其他集成测试
|
|
└── e2e/ # 端到端测试
|
|
├── api_test.go # API 测试
|
|
└── ... # 其他 E2E 测试
|
|
```
|
|
|
|
## Convey 框架概述
|
|
|
|
Convey 是一个 BDD 风格的 Go 测试框架,提供直观的语法和丰富的断言。
|
|
|
|
### 核心概念
|
|
|
|
- **Convey**: 定义测试上下文,类似于 `Describe` 或 `Context`
|
|
- **So**: 断言函数,验证预期结果
|
|
- **Reset**: 清理函数,在每个测试后执行
|
|
|
|
### 基本语法
|
|
|
|
```go
|
|
Convey("测试场景描述", t, func() {
|
|
Convey("当某个条件发生时", func() {
|
|
// 准备测试数据
|
|
result := SomeFunction()
|
|
|
|
Convey("那么应该得到预期结果", func() {
|
|
So(result, ShouldEqual, "expected")
|
|
})
|
|
})
|
|
|
|
Reset(func() {
|
|
// 清理测试数据
|
|
})
|
|
})
|
|
```
|
|
|
|
## 运行测试
|
|
|
|
### 运行所有测试
|
|
```bash
|
|
go test ./tests/... -v
|
|
```
|
|
|
|
### 运行特定类型的测试
|
|
```bash
|
|
# 单元测试
|
|
go test ./tests/unit/... -v
|
|
|
|
# 集成测试
|
|
go test ./tests/integration/... -v
|
|
|
|
# 端到端测试
|
|
go test ./tests/e2e/... -v
|
|
```
|
|
|
|
### 运行带覆盖率报告的测试
|
|
```bash
|
|
go test ./tests/... -v -coverprofile=coverage.out
|
|
go tool cover -html=coverage.out -o coverage.html
|
|
```
|
|
|
|
### 运行基准测试
|
|
```bash
|
|
go test ./tests/... -bench=. -v
|
|
```
|
|
|
|
## 测试环境配置
|
|
|
|
### 单元测试
|
|
- 不需要外部依赖
|
|
- 使用内存数据库或模拟对象
|
|
- 快速执行
|
|
|
|
### 集成测试
|
|
- 需要数据库连接
|
|
- 使用测试数据库 `v2_test`
|
|
- 需要启动 Redis 等服务
|
|
|
|
### 端到端测试
|
|
- 需要完整的应用环境
|
|
- 测试真实的 HTTP 请求
|
|
- 可能需要 Docker 环境
|
|
|
|
## Convey 测试最佳实践
|
|
|
|
### 1. 测试结构设计
|
|
- 使用描述性的中文场景描述
|
|
- 遵循 `当...那么...` 的语义结构
|
|
- 嵌套 Convey 块来组织复杂测试逻辑
|
|
|
|
```go
|
|
Convey("用户认证测试", t, func() {
|
|
var user *User
|
|
var token string
|
|
|
|
Convey("当用户注册时", func() {
|
|
user = &User{Name: "测试用户", Email: "test@example.com"}
|
|
err := user.Register()
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("那么用户应该被创建", func() {
|
|
So(user.ID, ShouldBeGreaterThan, 0)
|
|
})
|
|
})
|
|
|
|
Convey("当用户登录时", func() {
|
|
token, err := user.Login("password")
|
|
So(err, ShouldBeNil)
|
|
So(token, ShouldNotBeEmpty)
|
|
|
|
Convey("那么应该获得有效的访问令牌", func() {
|
|
So(len(token), ShouldBeGreaterThan, 0)
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
### 2. 断言使用
|
|
- 使用丰富的 So 断言函数
|
|
- 提供有意义的错误消息
|
|
- 验证所有重要的方面
|
|
|
|
### 3. 数据管理
|
|
- 使用 `Reset` 函数进行清理
|
|
- 每个测试独立准备数据
|
|
- 确保测试间不相互影响
|
|
|
|
### 4. 异步测试
|
|
- 使用适当的超时设置
|
|
- 处理并发测试
|
|
- 使用 channel 进行同步
|
|
|
|
### 5. 错误处理
|
|
- 测试错误情况
|
|
- 验证错误消息
|
|
- 确保错误处理逻辑正确
|
|
|
|
## 常用 Convey 断言
|
|
|
|
### 相等性断言
|
|
```go
|
|
So(value, ShouldEqual, expected)
|
|
So(value, ShouldNotEqual, expected)
|
|
So(value, ShouldResemble, expected) // 深度比较
|
|
So(value, ShouldNotResemble, expected)
|
|
```
|
|
|
|
### 类型断言
|
|
```go
|
|
So(value, ShouldBeNil)
|
|
So(value, ShouldNotBeNil)
|
|
So(value, ShouldBeTrue)
|
|
So(value, ShouldBeFalse)
|
|
So(value, ShouldBeZeroValue)
|
|
```
|
|
|
|
### 数值断言
|
|
```go
|
|
So(value, ShouldBeGreaterThan, expected)
|
|
So(value, ShouldBeLessThan, expected)
|
|
So(value, ShouldBeBetween, lower, upper)
|
|
```
|
|
|
|
### 集合断言
|
|
```go
|
|
So(slice, ShouldHaveLength, expected)
|
|
So(slice, ShouldContain, expected)
|
|
So(slice, ShouldNotContain, expected)
|
|
So(map, ShouldContainKey, key)
|
|
```
|
|
|
|
### 字符串断言
|
|
```go
|
|
So(str, ShouldContainSubstring, substr)
|
|
So(str, ShouldStartWith, prefix)
|
|
So(str, ShouldEndWith, suffix)
|
|
So(str, ShouldMatch, regexp)
|
|
```
|
|
|
|
### 错误断言
|
|
```go
|
|
So(err, ShouldBeNil)
|
|
So(err, ShouldNotBeNil)
|
|
So(err, ShouldError, expectedError)
|
|
```
|
|
|
|
## 测试工具
|
|
|
|
- `goconvey/convey` - BDD 测试框架
|
|
- `gomock` - Mock 生成器
|
|
- `httptest` - HTTP 测试
|
|
- `sqlmock` - 数据库 mock
|
|
- `testify` - 辅助测试工具(可选)
|
|
|
|
## 测试示例
|
|
|
|
### 配置测试示例
|
|
```go
|
|
Convey("配置加载测试", t, func() {
|
|
var config *Config
|
|
|
|
Convey("当从文件加载配置时", func() {
|
|
config, err := LoadConfig("config.toml")
|
|
So(err, ShouldBeNil)
|
|
So(config, ShouldNotBeNil)
|
|
|
|
Convey("那么配置应该正确加载", func() {
|
|
So(config.App.Mode, ShouldEqual, "development")
|
|
So(config.Http.Port, ShouldEqual, 8080)
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
### 数据库测试示例
|
|
```go
|
|
Convey("数据库操作测试", t, func() {
|
|
var db *gorm.DB
|
|
|
|
Convey("当连接数据库时", func() {
|
|
db = SetupTestDB()
|
|
So(db, ShouldNotBeNil)
|
|
|
|
Convey("那么应该能够创建记录", func() {
|
|
user := User{Name: "测试用户", Email: "test@example.com"}
|
|
result := db.Create(&user)
|
|
So(result.Error, ShouldBeNil)
|
|
So(user.ID, ShouldBeGreaterThan, 0)
|
|
})
|
|
})
|
|
|
|
Reset(func() {
|
|
if db != nil {
|
|
CleanupTestDB(db)
|
|
}
|
|
})
|
|
})
|
|
```
|
|
|
|
### API 测试示例
|
|
```go
|
|
Convey("API 端点测试", t, func() {
|
|
var server *httptest.Server
|
|
|
|
Convey("当启动测试服务器时", func() {
|
|
server = httptest.NewServer(NewApp())
|
|
So(server, ShouldNotBeNil)
|
|
|
|
Convey("那么健康检查端点应该正常工作", func() {
|
|
resp, err := http.Get(server.URL + "/health")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
|
|
|
var result map[string]interface{}
|
|
json.NewDecoder(resp.Body).Decode(&result)
|
|
So(result["status"], ShouldEqual, "ok")
|
|
})
|
|
})
|
|
|
|
Reset(func() {
|
|
if server != nil {
|
|
server.Close()
|
|
}
|
|
})
|
|
})
|
|
```
|
|
|
|
## CI/CD 集成
|
|
|
|
测试会在以下情况下自动运行:
|
|
- 代码提交时
|
|
- 创建 Pull Request 时
|
|
- 合并到主分支时
|
|
|
|
测试结果会影响代码合并决策。Convey 的详细输出有助于快速定位问题。 |