这份文档用于给当前仓库提供一个可直接复用的系统性测试模板。
这里的“系统性测试”不是简单单元测试,而是围绕一个完整链路或一个跨 crate 行为做验证:
- 有明确输入
- 有稳定 fixture
- 有可比较输出
- 有固定验证命令
同时,这份文档也定义一个默认工作流:
- 先写契约和验收项
- 再补 fixture
- 再写失败测试
- 最后才进入实现
如果一个工作包还没有 fixture、测试入口和验证命令,就不应视为“开始实现完成”。
当前仓库里的系统性测试,优先覆盖这些场景:
- 配置加载到运行上下文的链路
- 抓取结果到统一领域模型的归一化
- 调度、过滤、聚合、排序的组合逻辑
- 存储写入后的结果一致性
- 结构化输出和快照结果
在进入完整功能迁移前,当前仓库已经有一条真实、可重复执行的最小系统链路:
- 链路:
config -> app::bootstrap - 合法 fixture:
fixtures/system/config/minimal-valid.json - 非法 fixture:
fixtures/system/config/invalid-empty-timezone.json - 测试入口:
crates/app/tests/config_to_bootstrap.rs - 验证命令:
cargo test --workspace
后续新增系统性测试时,建议先模仿这条链路,再扩展到抓取、分析、存储和输出组合测试。
建议按下面方式组织:
src/
lib.rs
tests/
common/
mod.rs
system.rs
system/
README.md
config_to_bootstrap.rs
fetch_to_domain.rs
analyze_pipeline.rs
storage_to_report.rs
fixtures/
system/
config/
fetch/
analyze/
storage/
report/
tests/snapshots/
说明:
- 根工作区通过
src/lib.rs提供一个最小 test harness tests/system.rs作为系统性测试入口,挂载tests/system/*.rstests/common/mod.rs放共享 fixture loader、断言 helper、固定时间构造等通用能力- fixture 继续统一放在
fixtures/system/下,避免散落到各 crate
每个系统测试建议都具备这 5 部分:
- 测试目标:要验证哪条链路。
- 输入 fixture:稳定的 JSON、YAML、文本或内联样例。
- 执行步骤:调用哪些 crate 或公开函数。
- 期望结果:结构化断言或快照。
- 验证入口:本地和 CI 用哪条命令跑。
新增一个模块或工作包时,默认按下面顺序推进:
- 在契约文档中写明输入、输出、错误语义
- 在验收矩阵中补 fixture / 测试入口 / 最低验证命令
- 先补最小 fixture,必要时同时补合法与非法样例
- 先写 crate 内测试,再写系统性测试
- 确认测试先失败,再补实现
- 实现完成后,回填快照或更完整的结构断言
- 最后跑
cargo test --workspace,必要时再跑just verify
不要采用“先把功能写完,最后再集中补测试”的方式。
//! 示例:系统性测试模板
use anyhow::Result;
#[test]
fn system_case_name() -> Result<()> {
// 1. 准备 fixture
// let input = crate::common::load_json_fixture("...")?;
// 2. 调用跨 crate 链路
// let config = trendradar_config::...;
// let output = trendradar_fetch::...;
// 3. 做结构化断言
// assert_eq!(output.xxx, expected.xxx);
// 4. 如有必要,做 snapshot
// insta::assert_json_snapshot!(output);
Ok(())
}- 单元测试:
放在 crate 的
src/内,验证纯函数、局部规则、错误分支 - crate 级集成测试:
放在各 crate 自己的
tests/目录,验证一个 crate 对外暴露的稳定接口 - 工作区系统测试:
放在根
tests/system/*.rs,验证跨 crate 链路和固定 fixture
判断标准:
- 如果只保护一个 crate 内部规则,用单元测试
- 如果要验证 crate 对外 API,用 crate 集成测试
- 如果涉及两个及以上 crate,用系统性测试
新增系统性测试时,建议先写一段 case 描述,再实现代码:
## 用例名称
### 目标
验证哪条业务链路。
### 输入
- fixture 文件:
- 来源:
- 是否脱敏:
### 执行步骤
1. 加载配置或输入样例
2. 调用对应 crate / 函数
3. 收集输出
### 断言
- 结构断言:
- 数值断言:
- 错误断言:
- snapshot:
### 覆盖的风险
- 这条测试在防止什么回归- 测试文件名使用“链路”或“能力”命名
- fixture 目录按能力分组,不按临时人名或日期命名
- snapshot 名称与测试函数保持一致
- fixture 读取优先通过
tests/common/mod.rs的共享 loader 完成 - 通用 helper 只放“读取样例、固定时间、共享断言”这类稳定能力
- 不要把业务逻辑偷偷塞进 helper,否则系统测试会失去可读性
- 对顶层 JSON 输出、聚合结果、系统链路结果,优先考虑
insta - 对数值小、结构稳定的结果,可以用 inline snapshot
- 当输出结构变大、需要多人审查时,再切到
tests/snapshots/文件快照 - snapshot 更新必须和 fixture / 契约更新一起出现,不能只改 snapshot 不解释原因
在当前仓库中,系统性测试至少应纳入下面这些入口:
cargo test --workspace
cargo nextest run --workspace --all-features
cargo test --doc --workspace如果后续引入 snapshot 或覆盖率,还应补:
cargo llvm-cov nextest --workspace --all-features在首版迁移阶段,一个 crate 要被视为“具备基础系统验证”,至少应满足:
- 有一个跨函数或跨 crate 的系统性测试样例
- 有对应 fixture 或明确的内联样例
- 能用固定命令重复执行
- 能从失败结果中看出是哪条链路出错
- 验收矩阵已标出测试入口和最低验证命令
当前仓库已经不只一条真实样例,而是形成了几条可复用链路:
crates/app/tests/config_to_bootstrap.rs先把fixture -> config -> app的最小入口跑通tests/system/fetch_to_domain.rs覆盖fetch -> domain的正常、空输入、错误路径与双来源同时为空tests/system/analyze_pipeline.rs覆盖config -> schedule -> analyze的允许门控、禁止门控、同排名排序、零排名边界与空输入tests/system/storage_to_report.rs覆盖storage -> report的有数据、空数据、去重、来源主键和排序稳定性tests/system/app_pipeline_modes.rs覆盖app全链路的最小正向闭环、空来源闭环、单来源闭环、RSS-only 闭环、hotlist-only 闭环、跨午夜窗口内放行 / 窗口外阻断闭环、collect=false时跳过损坏 source、窗口阻断时跳过损坏 source、collect-only时仍传播损坏 source 错误、窗口放行时仍传播损坏 source 错误的路径、8 个collect/analyze/push布尔组合和上游解析错误透传tests/system/config_schedule_errors.rs覆盖配置 / 调度边界的默认值回退、成功窗口判定与错误路径
- 当前根级系统测试已经覆盖:
fetch -> domain的正常、空输入、错误路径与双来源同时为空,config -> schedule的默认值回退、窗口成功 / 失败与错误路径,config -> schedule -> analyze的允许门控、禁止门控、tie-break、零排名和空输入,storage -> report的有数据、空数据、去重、来源主键和排序稳定性, 以及app全链路的最小正向闭环、空来源闭环、单来源闭环、RSS-only 闭环、hotlist-only 闭环、跨午夜窗口内放行 / 窗口外阻断闭环、collect=false时跳过损坏 source、窗口阻断时跳过损坏 source、collect-only时仍传播损坏 source 错误、窗口放行时仍传播损坏 source 错误的路径、8 个collect/analyze/push布尔组合和上游解析错误透传。 - 继续补更多跨 crate richer cases,而不是只停留在 crate 内边界测试
app的阶段门控、来源形态、动态窗口和错误传播矩阵已经比较完整,下一批 richer cases 更适合优先转向非app链路- 优先扩
fetch -> analyze、storage -> report或后续新增链路中的系统级错误路径、空输入路径和结构快照 - 在适合快照的链路上继续引入更明确的结构对比