小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告;大中型测试会带来整体产品质量和数据验证。
不同类型的项目对测试的需求不同,总体上有 70/20/10 经验法则:70% 小型测试,20% 中型测试,10% 大型测试。
如果一个项目是面向用户的,拥有较高的集成度或用户接口比较复杂,就应该有更多的中型和大型测试;如果是基础平台或者面向数据的项目(例如索引或网络爬虫),则最好有大量的小型测试。
# 单元测试
单元测试的基本要求:
- 快速
- 环境一致
- 任意顺序
- 并行
基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行 unittest 场景下的容器依赖问题:
- 本地安装 Docker。
- 无侵入式的环境初始化。
- 快速重置环境。
- 随时随地运行(不依赖外部服务)。
- 语义式 API 声明资源。
- 真实外部依赖,而非 in-process 模拟。
包含测试的项目目录结构:
|-- service
|-- api
|-- cmd
|-- configs
|-- internal
|-- test
|-- docker-compose.yaml
|-- database.sql
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
要满足以下原则:
- 正确地对容器内服务进行健康检测,避免测试启动时资源还未准备好。
- 应该交由 app 自己初始化数据,比如 db 的 scheme,初始 sql 数据等。为了满足测试的一致性,在每次结束后都会销毁容器。
- 在单元测试开始前导入封装好的 testing 库,方便启动和销毁容器。
- 对于 service 的单元测试,使用 gomock 等库把 mock DAO 层。在设计包时,应该面向接口编程。
- 在本地启动依赖 Docker 容器,在 CI 环境里执行单元测试,需要考虑物理机中的容器网络,或在容器里再次启动一个 Docker。
func TestMain(m *testing.M) {
flag.Set("f", "./test/docker-compose.yaml")
flag.Parse()
if err := lich.Setup(); err != nil {
panic(err)
}
defer lich.Teardown()
if ret := m.Run(); ret != 0 {
panic(ret)
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 最佳实践
利用 go 官方提供的 Subtests + Gomock 完成整个单元测试。对于每层代码:
- /api:更适合进行集成测试,使用 API 测试框架(YApi)维护大量业务测试 case。
- /data:使用 docker compose 模拟底层基础设施,可以去掉 infra 的抽象层。
- /biz:依赖 repo、rpc client,利用 gomock 模拟 interface 实现来进行业务单元测试。
- /service:依赖 biz 实现,构建 biz 实现类传入进行单元测试。
一般的开发测试流程:
- 基于 git branch 进行 feature 开发。
- 开发过程,在本地执行单元测试。
- 提交 gitlab merge request 进行 CI 的单元测试。
- 基于 feature branch 进行构建。
- 完成功能测试之后合并 master。
- 上线前进行集成测试。
- 上线后进行回归测试。