单元测试
本文引用的文件
- README.md
- Makefile
- tests/README.md
- src/admin_apps_runtime_test.v
- examples/codexbot-app/phpunit.xml
- examples/codexbot-app/composer.json
- examples/codexbot-app/tests/Feature/CodexBotAppFlowTest.php
- php/package/composer.json
- php/package/tests/cache_client_protocol_test.php
目录
简介
本指南面向 VHTTPD 项目的测试实践,覆盖以下范围: - V 语言单元测试的编写规范、组织方式与断言使用 - PHP 包(VPhp/VHttpd)的测试框架配置与用例编写(PHPUnit/Pest) - 异步代码(协程/事件循环)的测试策略 - 测试数据准备与管理(模拟对象、测试夹具) - 覆盖率统计的配置与报告生成建议
项目结构
VHTTPD 的测试分为三层: - V 单元/集成测试:与源码同目录,按模块组织,通过 Makefile 自动发现并运行 - PHP 包测试:位于 php/package/tests,直接以脚本形式执行 - E2E 测试:位于 tests/e2e,基于 shell 脚本对编译产物进行端到端验证
graph TB
A["根目录"] --> B["src/*_test.v<br/>V 单元测试与源码同级"]
A --> C["php/package/tests/*_test.php<br/>PHP 包测试"]
A --> D["tests/e2e/*.sh<br/>E2E 测试"]
A --> E["Makefile<br/>自动化构建与测试入口"]
A --> F["examples/codexbot-app/*<br/>示例应用含 Pest/PHPUnit 配置"]
章节来源 - tests/README.md:1-38 - Makefile:37-71
核心组件
- V 测试驱动与发现
- 通过 Makefile 的 test-fast/test-inproc/test-codexbot 等目标,自动发现 src 下的 *_test.v 并按需按文件或目录运行
- 子模块测试需要按目录运行,以便 V 编译器包含同目录的其他模块文件
- PHP 包测试
- php/package/tests 下的 *_test.php 由 Makefile 的 test-php 目标逐一执行
- 示例应用 examples/codexbot-app 提供 Pest/PHPUnit 配置与脚本命令
- E2E 测试
- tests/e2e/run.sh 和 smoke_test.sh 用于在构建后对 vhttpd 二进制进行冒烟与集成验证
章节来源 - Makefile:163-177 - tests/README.md:13-22
架构总览
下图展示了测试在整体工程中的位置与调用关系。
flowchart TD
Dev["开发者"] --> M["Makefile 目标<br/>test / test-fast / test-php / test-e2e"]
M --> VUnit["V 单元测试<br/>v test src/*_test.v"]
M --> PhpTests["PHP 包测试<br/>php php/package/tests/*_test.php"]
M --> E2E["E2E 测试<br/>bash tests/e2e/run.sh"]
VUnit --> Src["src/*<br/>被测模块"]
PhpTests --> Pkg["php/package/src/*<br/>被测库"]
E2E --> Bin["vhttpd 二进制"]
图表来源 - Makefile:163-177 - tests/README.md:23-38
详细组件分析
V 语言单元测试指南
- 命名规范
- 文件名:*_test.v
- 模块声明:module main(必须与被测模块同目录)
- 组织结构
- 顶层函数式测试:src/*_test.v
- 子模块测试:放在对应子目录下,并通过目录方式运行
- 断言使用
- 使用 assert 进行条件断言;失败时测试立即失败
- 运行方式
- 快速单测:make test-fast
- 指定文件:make test src/foo_test.v
- 子模块目录:make test-fast(自动发现并按目录运行)
sequenceDiagram
participant Dev as "开发者"
participant MK as "Makefile"
participant V as "V 编译器"
participant T as "*_test.v"
Dev->>MK : make test-fast
MK->>V : v test <files_or_dirs>
V->>T : 编译并运行测试
T-->>V : assert 结果
V-->>Dev : 通过/失败输出
图表来源 - Makefile:163-171 - tests/README.md:13-22
章节来源 - src/admin_apps_runtime_test.v:1-77 - tests/README.md:13-22 - Makefile:37-71
PHP 包单元测试(PHPUnit/Pest)
- 配置与入口
- 示例应用使用 Pest 作为主测试框架,同时兼容 PHPUnit 配置
- composer.json 中定义 dev 依赖与脚本命令,便于一键运行
- phpunit.xml 定义测试套件目录与源码覆盖范围
- 测试组织
- 用例位于 examples/codexbot-app/tests/Feature/*.php
- 使用 it('...') 风格描述行为,结合 expect() 断言
- 运行方式
- 通过 Composer 脚本 pest 或 test 运行
- 也可通过 phpunit.xml 直接运行 PHPUnit
flowchart TD
A["composer.json scripts"] --> B["vendor/bin/pest -c phpunit.xml"]
B --> C["tests/Feature/*.php"]
C --> D["expect()/assert* 断言"]
图表来源 - examples/codexbot-app/composer.json:22-36 - examples/codexbot-app/phpunit.xml:1-18
章节来源 - examples/codexbot-app/composer.json:1-43 - examples/codexbot-app/phpunit.xml:1-18 - examples/codexbot-app/tests/Feature/CodexBotAppFlowTest.php:1-120
异步代码测试策略(协程/事件循环)
- 原则
- 避免阻塞事件循环:尽量使用非阻塞 I/O 与超时控制
- 明确生命周期:为每个测试设置启动/清理钩子,确保资源释放
- 可确定性:固定时间源、随机种子与外部依赖注入,保证可重复性
- 推荐模式
- 使用轻量级“测试运行时”封装事件循环,提供 runUntilDone/waitForEvent 等辅助方法
- 将外部依赖(网络、数据库、文件系统)替换为内存/内存套接字/临时文件
- 针对回调/事件流,采用“等待完成信号”而非 sleep
- 参考实现思路
- 进程内协程:以任务队列+调度器为核心,测试中注入 mock 任务与期望输出
- 事件总线:测试订阅关键事件,断言事件序列与载荷
[本节为通用方法论,不直接分析具体文件]
测试数据准备与管理(模拟对象与测试夹具)
- 测试夹具
- 使用工厂函数创建最小可用环境(如临时目录、内存数据库、Unix Socket)
- 在 setUp/tearDown 或 beforeAll/afterAll 中集中管理资源
- 模拟对象
- 对外部系统(网络、DB、FS)提供接口抽象,测试中注入 Mock/Fixture
- 对于协议层(如帧编解码),构造最小合法报文进行端到端校验
- 示例参考
- 缓存客户端协议测试通过 fork 子进程模拟服务端,验证请求/响应帧格式与业务字段
sequenceDiagram
participant Test as "测试用例"
participant Srv as "子进程(模拟服务端)"
participant Cli as "Client(被测)"
Test->>Srv : 启动 Unix Socket 服务
Test->>Cli : 实例化 Client
Cli->>Srv : 发送 set(keys, ttl)
Srv-->>Cli : 返回 ok
Cli->>Srv : 发送 keys(namespace)
Srv-->>Cli : 返回 keys 列表
Test->>Test : 断言返回结构与内容
Test->>Srv : 关闭服务/清理资源
图表来源 - php/package/tests/cache_client_protocol_test.php:1-80
章节来源 - php/package/tests/cache_client_protocol_test.php:1-80
覆盖率统计与报告生成
- V 语言
- 当前仓库未内置覆盖率门禁与报告生成步骤
- 建议在 CI 中引入 V 支持的覆盖率选项(若可用)或在 Makefile 中扩展 test 目标
- PHP
- 示例应用已包含 PHPUnit 配置,可通过扩展 phpunit.xml 启用 coverage 输出(例如 HTML/XML)
- 可在 composer.json 中新增脚本命令,统一生成与归档覆盖率报告
[本节为通用建议,不直接分析具体文件]
依赖分析
- 构建与测试入口
- Makefile 负责自动发现 V 与 PHP 测试文件,并提供多组测试目标
- 示例应用测试依赖
- Pest 作为开发依赖,配合 phpunit.xml 运行
- PHP 包测试
- 直接以 php 命令执行 *_test.php,无需额外框架
graph LR
MK["Makefile"] --> VF["V 测试发现与运行"]
MK --> PF["PHP 包测试发现与运行"]
EX["examples/codexbot-app"] --> PE["Pest + PHPUnit 配置"]
PKG["php/package/tests"] --> PH["php *_test.php"]
图表来源 - Makefile:163-177 - examples/codexbot-app/composer.json:11-16 - examples/codexbot-app/phpunit.xml:7-11
章节来源 - Makefile:163-177 - examples/codexbot-app/composer.json:1-43 - examples/codexbot-app/phpunit.xml:1-18
性能考虑
- 测试隔离与并行
- 将重型 in-proc 测试与快速单测分离,减少 CI 时长
- 对无状态测试可考虑并行执行(注意共享资源竞争)
- 资源复用
- 复用临时数据库/套接字/文件句柄,避免频繁创建销毁
- 外部依赖
- 尽可能使用内存或本地回退实现,降低网络/磁盘抖动影响
[本节为通用建议,不直接分析具体文件]
故障排查指南
- V 测试
- 确认 *_test.v 与源码同目录且 module main 正确
- 子模块测试请通过目录运行,避免裸文件编译缺失依赖
- PHP 包测试
- 检查 phpunit.xml 的 bootstrap 与 testsuite 目录是否正确
- 确认 composer 依赖安装成功,脚本命令可执行
- E2E 测试
- 先执行 make build 生成二进制,再运行 tests/e2e/run.sh
章节来源 - tests/README.md:13-38 - Makefile:163-177
结论
- V 测试遵循“与源码同级、按模块组织、自动发现”的原则,适合快速迭代
- PHP 包测试以独立脚本为主,示例应用提供了 Pest/PHPUnit 的完整配置范式
- 异步测试应围绕“非阻塞、可确定性、资源可控”展开
- 覆盖率目前未内置门禁,可按需扩展至 Makefile 与 CI 流程
[本节为总结,不直接分析具体文件]
附录
- 常用命令
- V 快速单测:make test-fast
- 指定 V 测试文件:make test src/foo_test.v
- PHP 包测试:make test-php
- E2E 测试:bash tests/e2e/run.sh
- 参考文档
- 项目概览与运行说明见 README.md
章节来源 - README.md:1-150 - tests/README.md:23-38