测试——从最小单元到完整系统的自动化断言

测试 = 自动化断言「代码按设计工作」。 一个程序拆成了系统、模块、函数,测试就是把每一层「按设计工作」用可重复的断言锁死。测试不是一种,而是一串从最小单元到完整系统的步骤——每一步测的东西不同、依据不同、工具不同。本篇讲这条链:CLI/GUI/游戏/网络四种程序类型各自怎么测,重点展开游戏(引擎系统 + 游戏系统)。


一、测试链:从最小单元到完整系统

测试是一条从内到外的链,每一环测的是不同层级的「按设计工作」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
单元测试    最小逻辑单元(算法 / 纯函数 / 组件数据)—— 不依赖外部

系统测试 多个系统组合(ECS 系统协作)—— 真实内部数据

数据驱动 配置文件 / 数据合法性(字段、数值范围、引用)

状态测试 状态转换合法性(合法转换允许、非法转换禁止)

关卡/场景 地图 / 场景数据(内容完整性、可达性、数值)

集成测试 完整流程链路(不跳层,真实数据串联多个接口)

用户流程 用户视角完整链路(操作 → 系统 → 反馈)

E2E 真实二进制、真实输入、断言真实输出/画面

测试链的结构 = 程序结构的镜像。 程序拆成几层,测试就分成几层——每一层测「这一层按设计工作」。

贯穿测试链的三个纪律

1
2
3
① 失败必跳回:任何测试失败都要跳回对应实现/设计步骤修复后重测,禁止带失败继续往下
② 覆盖充分:声明支持的能力必须有正例测试,不支持的形式必须有负例测试,未覆盖即缺陷
③ 副作用检查:对被操作对象有副作用的测试,执行后必须断言对象仍处于预期状态

用例先行

每层测试(尤其集成/E2E)都先写测试用例清单,再写代码/脚本:

1
2
3
4
5
用例 = (场景 / 前置条件 / 操作步骤 / 预期结果)
场景:哪个功能、哪个流程
前置:需要什么初始状态、什么真实数据
操作:精确到输入方式(命令、按键、鼠标)
预期:可被断言的结果(输出、退出码、画面状态、数据变化)

用例是「功能需求 → 可执行验证」的桥梁,防止「有功能无入口、有入口无验证」。


二、CLI 的测试:业务系统静态库 + 入口系统分开测

CLI 程序分为入口系统业务系统。系统可以做成库——业务系统做成静态库,单独测试单独验证

业务系统(静态库)的测试

1
2
3
4
5
6
7
8
9
10
11
单元测试(gtest)    只测算法层与纯计算能力(无 I/O 的纯函数)
依据:算法分析文档(测试点、边界条件、输入输出)
不测试其他层

接口测试(gtest) 用真实数据只测功能函数/接口(Service 接口)
依据:API 接口列表 + 异常设计
在真实场景测试(test/target 配合)

集成测试(gtest) 用真实数据串联多个接口完成完整业务流程
依据:业务流程分析文档 → 先写用例清单(场景/前置/真实数据/操作序列/预期)
覆盖正常路径与异常路径

真实数据测试依据功能点/业务流程先写测试用例——不是拍脑袋写,而是从业务流程分析文档逐条翻译成用例清单,再据此写测试代码。

CLI(链接业务系统静态库后)的测试

1
2
3
4
5
6
7
8
9
10
单元测试(gtest)    只测 CLI 的解析与分发(命令层/参数层/接口调用层内部)
不测试业务逻辑(业务在静态库里已测过)

API 集成测试 走通 parse → execute → 静态库公共 API 完整链路(不跳层)
验证 CLI 通过接口调用层正确调用静态库 API

E2E(Python 脚本) 测试 CLI 真实运行——真实二进制、注入真实输入、断言真实输出/退出码
独立体系(test/e2e/,不混入 CMake)

手动验证 真实运行命令,人工确认结果符合预期

CLI 测试的关键:业务和入口分开测。 业务系统(静态库)单独测完,CLI 只测自己的解析与分发——不重复测业务,只验证「入口能正确调用业务」。


三、GUI 的测试:交互、用户流程、状态三块

GUI 程序的结构拆成「页面(交互)+ 调用(状态)」,测试也拆成对应的三块:

1
2
3
4
5
6
7
8
9
10
11
12
13
单元测试     Service / ViewModel / Components / Domain/Utils
(mock 基础设施,测各自逻辑)

集成测试 MVVM 完整数据流(View 信号 → ViewModel → Service → ViewModel → View 更新)
页面导航(页面切换、弹窗调用)

用户流程测试 模拟用户操作 → VM 接收事件 → 触发业务流程 → 更新属性 → View 刷新
以用户视角验证完整业务流程真实完成(断言真实业务结果)
每个用户流程(场景)一个用例

状态测试 ViewModel 状态变化 → View 正确刷新(状态变化界面验证)

E2E 真实启动程序、注入真实操作、断言真实画面(像素)

GUI-MVVM 里验证分两种(对应入口拆成「页面 + 调用」):

1
2
交互验证:用户操作 → View 正确转发(控件交互、事件转发、显示状态)
状态变化界面验证:ViewModel 状态变化 → View 正确刷新(信号收到、界面刷新、显示正确)

GUI 测试的关键:验证的拆分和结构的拆分一致。 入口拆成「页面 + 调用」,验证就拆成「交互 + 状态变化界面」。


四、游戏的测试:引擎系统 + 游戏系统分开测

游戏分为引擎系统(可复用的系统集合)和游戏系统(利用这些系统构建的具体系统)。和 CLI 一样,引擎做成静态库 libengine.a单独测试单独验证;游戏链接引擎后测自己的逻辑。

引擎系统(静态库)的测试

1
2
3
4
5
6
7
8
单元测试(gtest)    ecs / math / resource(算法、组件数据)
测一个组件/算法/类,不依赖其他

集成测试(gtest) rendering / physics / audio(多个引擎组件能否一起工作)
测子系统协作

Example / Demo 真实运行引擎(render_triangle → render_texture → physics_demo)
验证引擎作为整体真的能被使用

图形引擎不能只做单元测试——很多东西依赖真实 GPU。从 render_triangle(Window→Renderer→Shader→Triangle→GPU)开始,不要一开始就做完整游戏。

游戏系统(链接引擎后)的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
单元测试    游戏规则 / 算法 / 数据处理(DamageRule 等)
不需要启动游戏

系统测试 ECS 系统组合(真实 Registry 测 MovementSystem 更新后 Transform 位移正确)
测试系统间事件通信(碰撞触发事件 → HealthSystem 处理)

数据驱动 配置文件字段完整性、数值范围、引用有效性(enemy.json 等)
典型:hp 不能为负、damage 必须为正、item 图标路径必须存在

状态测试 实体状态合法/非法转换(Idle→Run→Jump→Fall→Dead)
合法转换允许、非法转换禁止(Dead→Jump 应被拒绝)

关卡测试 地图数据:六要素齐全(出生点/地面/挑战/收集/终点/判定)
丰富项落地、出口可达性(BFS)、敌人数量上限、无负值

集成测试 完整游戏循环(初始化→输入→系统执行→渲染→关闭)
系统按顺序执行、事件分发、延迟销毁、渲染调用

用户流程测试 模拟玩家操作(输入)→ 系统执行(调渲染库)→ 结果反馈
每个用户流程(场景)一个用例,使用真实渲染库数据
覆盖正常流程与异常流程

E2E 真实启动 exe、注入真实操作、断言真实画面(像素)
独立体系(test/e2e/,不混入 CMake)

游戏测试层级(与普通软件对照)

1
2
3
4
5
6
7
8
普通软件:算法 → 模块 → 系统 → 产品
游戏: 函数 → 组件 → 游戏系统 → 关卡 → 完整游戏体验

Game
├── Unit Test 游戏规则 / 算法 / 数据处理
├── System Test 游戏 System + Engine(ECS/Render)
├── Integration 完整游戏场景 / 流程
└── 再往上:自动化功能测试 → 性能测试 → 压力测试 → 兼容性测试 → 发布测试

游戏测试最关键的一点:普通软件主要验证「功能有没有实现」;游戏还要验证「设计的世界规则运行后产生的体验是否符合预期」。 这也是为什么游戏测试不能只靠 gtest,必须有人玩、自动玩、模拟世界运行。


五、网络系统的测试:组件 + 接口 + 完整通信流程

网络系统(通信系统)的测试:

1
2
3
4
5
单元测试    单个组件内部逻辑(Buffer 读写指针、Connection 状态机)
接口测试 组件接口按设计工作(EventLoop 事件分发、Session 超时)
测试调的是接口——接口没定就没东西可测
E2E 客户端发请求 → 网络系统收发 → 入口解析分发 → 业务处理 → 响应回传
验证完整通信流程能跑通

网络系统的测试顺序依赖设计顺序:协议 → 组件 → 接口 → 实现 → 测试。接口依赖协议、测试依赖接口——顺序不能反。


六、四种程序类型的测试对照

程序类型 系统拆分 单元测试 集成/系统测试 用户流程 E2E
CLI 入口 + 业务(静态库) 算法(库) + 解析分发(入口) API 集成走通三层 —(直线/循环) Python 脚本真实运行
GUI 入口(页面+调用) + 业务 Service/VM/Components MVVM 数据流 + 导航 用户流程测试 真实启动+像素断言
游戏 引擎(静态库) + 游戏 算法/ECS组件(引擎) + 游戏规则(游戏) 系统组合+关卡+状态 玩家操作链路 真实启动+像素断言
网络 通信系统(组件) 组件内部逻辑 组件接口 完整通信流程

共同规律:

1
2
3
4
5
系统拆成几块 → 每块单独测(做成库的单独验证)
单元测最小逻辑(算法/纯函数)
集成测完整链路(真实数据、不跳层)
E2E 测真实运行(真实二进制、真实输入、真实输出/画面)
用户流程测用户视角(GUI/游戏特有)

七、与其他线的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
本线 ←→ 验证(06-验证,本主题内)
测试 = 自动化断言「代码按设计工作」(对不对)
验证 = 判断「做出来的东西是否符合预期/有价值」(好不好、能不能用、有没有意义)
测试可自动化,验证往往需要人/AI/Bot 参与

本线 ←→ 网络系统设计(网络系统/04)
网络系统的测试顺序依赖设计顺序(协议→组件→接口→实现→测试)

本线 ←→ 工程控制(07-工程控制)
工程控制讲「怎么保证工程不跑偏」——测试是其中「对不对」的手段
测试解决「对」,需求管理解决「做」和「全」

本线 ←→ 最小闭环
每个测试都是「输入→处理→输出→验证」的最小闭环
测试是闭环的验证层

本线 ←→ GUI-MVVM
验证的拆分和结构的拆分一致(交互验证 + 状态变化界面验证)

本线 ←→ 从输入到交互
用户流程测试测的是「用户流程文档」——从输入到交互线讲的就是这个

收束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
测试 = 自动化断言「代码按设计工作」

测试链(从内到外):
单元 → 系统 → 数据驱动 → 状态 → 关卡/场景 → 集成 → 用户流程 → E2E

四种程序类型:
CLI: 业务静态库(算法/接口/集成)+ 入口(解析分发)+ E2E
GUI: Service/VM/Components + MVVM 数据流 + 用户流程 + 状态 + E2E
游戏: 引擎静态库(单元/集成/Example)+ 游戏(规则/系统/数据/状态/关卡/流程/E2E)
网络: 组件内部 + 组件接口 + 完整通信流程

三个纪律:失败必跳回、覆盖充分(正例+负例)、副作用检查
用例先行:先写用例清单(场景/前置/操作/预期),再写代码/脚本
真实数据:接口/集成测试用真实数据,不用 mock

系统拆成几块 → 每块单独测(做成库的单独验证)
测试链 = 程序结构的镜像

测试回答「对不对」——代码有没有按设计工作。 但「做出来的东西好不好、能不能用、有没有意义」是另一件事——那是验证(见 06-验证,本主题内)。