Rust事件溯源测试清单:用eventually-rs的given-when-then Scenario测试聚合根
2026/8/28 15:02:18 网站建设 项目流程

Rust事件溯源测试清单:用eventually-rs的given-when-then Scenario测试聚合根

【免费下载链接】eventually-rsEvent Sourcing for Rust项目地址: https://gitcode.com/gh_mirrors/ev/eventually-rs

eventually-rs 是 Rust 生态中用于构建事件溯源(Event Sourcing)应用的核心库,它不仅提供了聚合根(Aggregate Root)与事件存储的抽象,还内置了Scenario测试类型——让你用经典的given-when-then三段式画布,快速、清晰地测试聚合根的行为。本文是一份实战清单,帮你在 Rust 项目中快速上手这套事件溯源测试方法。

一、30秒背景:事件溯源与聚合根

事件溯源系统不保存数据的"当前状态",而是把所有变更按顺序存为不可变事件,当前状态由事件日志回放(rehydrate)得出。

在 eventually-rs 中:

  • Aggregate(聚合):领域模型,状态只能通过领域事件变更,核心定义见eventually/src/aggregate/mod.rs
  • Root:聚合根的运行时载体,负责记录未提交事件、跟踪版本号
  • Scenario:专测聚合根行为的测试类型,位于eventually/src/aggregate/test.rs

💡 理解这三者,就理解了整个测试框架:Scenario 会用 given 的事件"回放"出一个 Root,执行 when 中的操作,再断言产生的事件是否符合预期。

二、核心工具:Scenario 三段式测试

Scenario::new()开启一个场景,链式调用即可读性极强:

Scenario::<MyAggregate>::new() .given(vec![...]) // 前置条件:已有事件 .when(|root| { ... }) // 执行动作:调用聚合根方法 .then(vec![...]) // 期望结果:应产生哪些事件 .assert(); // 执行并断言

前置条件:given —— 事件列表回放状态

given接收一组领域事件,Scenario 会自动将其折叠(fold)成聚合根的当前状态。这正好利用了事件溯源的本质:状态 = 事件历史

执行动作:when —— 两种分支

分支适用场景闭包签名
Scenario::when创建全新聚合根(无历史事件)Fn() -> Result<R, Err>
ScenarioGiven::when修改已存在的聚合根Fn(&mut R) -> Result<(), Err>

⚠️ 注意:given(...).when(...)分支如果给定事件无法成功回放出一个 Root,会直接 panic 并让测试失败——这是有意设计,防止测试前提本身出错。

断言结果:then / then_error / assert

  • .then(events):断言操作成功后恰好产生这些事件
  • .then_error(err):断言操作按预期失败并返回指定错误
  • .assert():执行整个场景,断言不通过则 panic 使测试失败

这套设计让你既测"快乐路径",也测"业务规则拒绝"的路径。

三、完整示例:灯开关聚合根走一遍

以官方 light-switch 示例为例(领域模型见examples/light-switch/src/domain.rs):

聚合根暴露了install(安装)、turn_on(开灯)、turn_off(关灯)等操作,并定义了AlreadyOnNotYetInstalled等业务错误。

一个典型的场景测试思路(成功路径):

Scenario::<LightSwitch>::new() .given(vec![LightSwitchEvent::Installed(...).into()]) // 已安装 .when(|root| root.turn_on(id)) // 尝试开灯 .then(vec![LightSwitchEvent::SwitchedOn(...).into()]) // 应产生开灯事件 .assert();

失败路径同样简单:

Scenario::<LightSwitch>::new() .given(vec![已安装, 已开灯 两个事件]) .when(|root| root.turn_on(id)) .then_error(LightSwitchError::AlreadyOn) // 已经开着了 .assert();

🎯 两段代码就完整覆盖了"开灯成功"和"重复开灯被拒"两条业务规则,无需 mock、无需数据库。

四、进阶:命令级 Scenario(command::test::Scenario)

当你要测试的是命令处理器(Command Handler)而不仅仅是聚合根时,eventually-rs 在eventually/src/command/test.rs提供了另一套 Scenario:

command::test::Scenario .given(vec![已持久化的事件]) // 系统前置状态 .when(命令.into()) // 直接投递命令 .then(vec![期望持久化的事件]) // 或 .then_fails() .assert_on(|event_store| Service::from(repo)) .await;

它与聚合级 Scenario 的关键区别:

  • given 用的是event::Persisted(含 stream_id 和 version),描述事件存储中的真实数据
  • then_fails:只断言失败,不指定具体错误
  • assert_on:注入一个内存事件存储构建被测服务,测试在真实(但内存中的)存储上跑完整链路

银行记账示例examples/bank-accounting/src/application.rs里就有一组完整示范:开户成功、重复开户失败、向已关闭账户存款失败、转账余额不足失败……每个场景一个测试函数,命名即文档。

五、实战清单:写 Event Sourcing 测试前对照

  • 每个业务不变量至少对应一个then_error场景(如"不能重复安装")
  • 每个领域事件都有一条成功路径的then断言
  • given覆盖状态机边界:初始态、中间态、终态
  • 聚合级用aggregate::test::Scenario,应用级用command::test::Scenario
  • 测试命名遵循"场景_条件_结果"句式(参考 bank-accounting 的测试函数命名)
  • 需要持久化行为时,用内存事件存储eventually/src/event/store.rs或 PostgreSQL 实现(eventually-postgres

六、关键文件导航

文件路径作用
eventually/src/aggregate/mod.rsAggregate trait 与 Root 实现
eventually/src/aggregate/test.rs聚合级 given-when-then Scenario
eventually/src/command/test.rs命令级 Scenario(then_fails / assert_on)
eventually/src/event/store.rs事件存储抽象与内存实现
examples/light-switch/src/domain.rs最简聚合根领域模型示例
examples/bank-accounting/src/application.rs命令级场景测试完整示范

按照这份清单,你的 Rust 事件溯源测试将同时做到可读(given-when-then 即业务语言)、可信(成功与失败路径双覆盖)、可维护(每个场景独立、命名即文档)。🚀

【免费下载链接】eventually-rsEvent Sourcing for Rust项目地址: https://gitcode.com/gh_mirrors/ev/eventually-rs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询