测试——从最小单元到完整系统的自动化断言
测试 = 自动化断言「代码按设计工作」。 一个程序拆成了系统、模块、函数,测试就是把每一层「按设计工作」用可重复的断言锁死。测试不是一种,而是一串从最小单元到完整系统的步骤——每一步测的东西不同、依据不同、工具不同。本篇讲这条链: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-验证,本主题内)。