模拟

对于模拟,Mockall 是一个广泛使用的库。您需要重构代码才能使用 trait,然后便可很快地对其进行模拟:

use std::time::Duration;

#[mockall::automock]
pub trait Pet {
    fn is_hungry(&self, since_last_meal: Duration) -> bool;
}

#[test]
fn test_robot_dog() {
    let mut mock_dog = MockPet::new();
    mock_dog.expect_is_hungry().return_const(true);
    assert_eq!(mock_dog.is_hungry(Duration::from_secs(10)), true);
}
This slide should take about 5 minutes.
  • 此处的建议适用于 Android (AOSP),其中推荐使用 Mockall 模拟库。crates.io 上还有其他模拟库可用,尤其是在模拟 HTTP 服务方面。其他模拟库的工作方式与 Mockall 类似,这意味着通过它们您可轻松实现对指定 trait 的模拟。

  • 请注意,模拟在某种程度上具有 争议性:借助模拟,您可以将测试与其依赖项完全隔离。最立竿见影的是,测试作业会更快且更稳定。另一方面,模拟对象的配置可能出现错误,并返回与真实依赖项不同的输出。

    建议您尽可能使用真实依赖项。例如,许多数据库都支持您配置内存后端。这意味着,您可以在测试中获得正确的功能行为,而且测试速度会很快并会自动清理。

    同样,许多 Web 框架都支持您启动进程内服务器,该服务器会绑定到 localhost 上的随机端口。相比模拟框架,请始终优先选择这种方式,因为这有助于您在真实环境中测试代码。

  • Mockall 不是 Rust Playground 的一部分,因此您需要在本地环境中运行此示例。使用 cargo add mockall 快速将 Mockall 添加到现有 Cargo 项目中。

  • Mockall 具有更多功能。具体而言,您可以设置基于传递参数的预期值。在这里,我们使用该功能来模拟一只猫,它在上次被喂食的 3 小时后会感到饥饿:

#[test]
fn test_robot_cat() {
    let mut mock_cat = MockPet::new();
    mock_cat
        .expect_is_hungry()
        .with(mockall::predicate::gt(Duration::from_secs(3 * 3600)))
        .return_const(true);
    mock_cat.expect_is_hungry().return_const(false);
    assert_eq!(mock_cat.is_hungry(Duration::from_secs(1 * 3600)), false);
    assert_eq!(mock_cat.is_hungry(Duration::from_secs(5 * 3600)), true);
}
  • 您可以使用 .times(n) 将调用模拟方法的次数限制为 n,如果不满足此条件,模拟对象被释放时会自动 panic。