Go工程化实践之测试

Testing

Unittest

单元测试

image-20231017173148561

  • 小型测试带来优秀的代码质量、良好的异常处理、优雅的错误报告;

    大中型测试会带来整体产品质量和数据验证。

  • 不同类型的项目,对测试的需求不同,总体上有一个经验法则:即70/20/10原则;

    70%是小型测试,20%是中型测试,10%是大型测试

  • 如果一个项目是面向用户的,拥有较高的集成度,或者用户接口比较复杂,他们就应该有更多的中型和大型测试;

    如果是基础平台或者面向数据的项目,例如索引或者爬虫,则最好有大量的小型测试,中型测试和大型测试的数量要求会少很多。

小型测试一般是单元测试,使用 go test 即可;

中型测试一般需要引入外包,例如 mock

大型测试则会更加整体,可能会使用 docker 构建服务进行自动化测试,一般会使用大型测试框架

“自动化实现的,用于验证一个单独函数或独立功能模块的代码是否按照预期工作,着重于典型功能性问题、数据损坏、错误条件和大小差一错误(译注:大小差一(off-by-one)错误是一类常见的程序设计错误)等方面的验证”

​ - 《Google软件测试之道》

image-20231017173928910

Basic Requirement

单元测试的基本要求:

  • 快速:运行和执行速度
  • 环境一致:单元测试前后不影响环境,开始之前可能会预处理环境,测试之后还原环境
  • 任意顺序:单元测试之间的顺序不构成依赖关系
  • 并行:提高效率

基于 docker-compose 实现跨平台跨语言环境的容器依赖管理方案,以解决运行 unittest 场景下的(MySQLRedismc)容器依赖问题:

image-20231017174245099

image-20231017174256653

  • 本地安装 Docker
  • 无侵入式的环境初始化
  • 快速重置环境
  • 随时随地运行(不依赖外部服务)
  • 语义式 API 声明资源
  • 真实外部依赖,而非 in-process 模拟
  • 正确的对容器内服务进行健康检测,避免 unittest 启动时候资源还未 ready
  • 应该交由 app 自己来初始化数据,比如 db 的 scheme,初始化 SQL 数据等,为了满足测试的一致性,在每次结束后,都会销毁容器。
  • 在单元测试开始前,导入封装好的 testing 库,方便启动和销毁容器
  • 对于 service 的单元测试,使用 gomock 等库,把 dao mock 掉,所以在设计包的时候,应该面向抽象编程
  • 在本地执行依赖 Docker,在 CI 环境里执行 Unittest ,需要考虑在物理机里的 Docker 网络,或者在 Docker 里再次启动一个 Docker。

image-20231017174437843

Completed Tests

利用 go 官方提供的:Subtests + Gomock 完成整个单元测试。

  • /api

    比较适合进行集成测试,直接测试 API,使用 API 测试框架(例如:yapi),维护大量业务测试 case

  • /data

    docker compose 把底层基础设施真实模拟,因此可以去掉 infra 的抽象层

  • /biz

    依赖 repo、rpc client,利用 gomock 模拟 interface 的实现,来进行业务单元测试

  • /service

    依赖 biz 的实现,构建 biz 的实现类传入,进行单元测试

基于 git branch 进行 feature 开发,本地进行 unittest,之后提交 gitlab merge request 进行 CI 的单元测试,基于 feature branch 进行构建,完成功能测试,之后合并 master,进行集成测试,上线后,进行回归测试。

Without integration tests, it’s difficult to trust the end-to-end operation of a web service.

编写易于测试的代码

  1. 外部传入实例,而不是内部自己创建;

    如果不行,就使用可以修改的包变量。最好是将 DBRPC 等访问第三方的做成成员变量,而后在创建的时候传入 mock 的版本

  2. 面向接口编程:因为不支持重写,所以只能面向接口编程

  3. 可以尝试将复杂逻辑抽取出来单独测试,所以需要的组件都是作为参数传入进去

参考

Integration Testing in Go: Part I - Executing Tests with Docker

Integration Testing in Go: Part II - Set-up and Writing Tests

Testable Examples in Go

Using Subtests and Sub-benchmarks

The cover story

Keeping Your Modules Compatible

Go Modules: v2 and Beyond

Testing with GoMock: A Tutorial

gomock

A GoMock Quick Start Guide