模拟

对于模拟,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.
  • Mockall is the recommended mocking library in Android (AOSP). There are other mocking libraries available on crates.io, in particular in the area of mocking HTTP services. The other mocking libraries work in a similar fashion as Mockall, meaning that they make it easy to get a mock implementation of a given 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。