2 个版本
0.3.2 | 2023 年 7 月 27 日 |
---|---|
0.3.1 | 2023 年 7 月 8 日 |
0.3.0 |
|
0.2.6 |
|
0.1.0 |
|
#192 在 测试 中排名
每月 90 次下载
24KB
291 行
stubby
- 无伤害的 stubbing
一个连 IDE 都能理解的微型 stubbing 库!
为什么使用 stub?
来自 维基百科
在单元测试中,模拟对象可以模拟复杂、真实对象的行为,因此当无法将真实对象纳入单元测试时非常有用。如果对象具有以下任何特征,则可能需要使用模拟对象
- 对象提供非确定性结果(例如当前时间或当前温度);
- 具有难以创建或再现的状态(例如网络错误);
- 运行缓慢(例如完整数据库,测试前需要准备);
- 尚不存在或可能改变行为;
- 需要包含仅用于测试目的的信息和方法(而不是其实际任务)。
使用示例
您可以使用 cargo add stubby
,或者在您的 Cargo.toml
中
[dependencies]
stubby = "0.3"
注意,stubby
必须放入您的常规依赖项,而不是开发依赖项。
现在,一些使用 stubby
的代码
use stubby::*;
struct TestStruct(StubbyState);
impl TestStruct {
fn foo(&self) -> i32 {
stub!(&self.0);
10
}
}
fn main() {
let ts = TestStruct(StubbyState::default());
assert_eq!(ts.foo(), 10);
}
#[test]
fn demo() {
let mut mock = StubbyState::default();
mock.insert(fn_name!(TestStruct::foo), 15);
let ts = TestStruct(mock);
assert_eq!(ts.foo(), 15);
}
Rust 中的模拟/stubbing 为什么如此困难?(与 mockall
的比较)
Rust 中的模拟困难是因为强类型和编译成机器码不提供任何灵活性来处理数据/行为,就像您在鸭子类型语言或具有某种解释器的语言中那样。
因此,在Rust中使用模拟库的常见方法是通过使用过程宏生成一个新的MockFoo
,该宏从你的implFoo
块生成,该块具有额外的功能,允许你自定义返回类型和进行复杂的操作。然后,在编译时,你可以选择使用#[cfg(not(test))]use lib::Foo
或#[cfg(test)]use lib::MockFoo as Foo
,这是可行的,因为生成的MockFoo
提供了与真实Foo
相同的所有方法。然而,由于Rust Analyzer和Jetbrains IDEs的Rust插件,这些编译时条件导入完全破坏了自动完成、类型提示,有时甚至语法高亮。此外,即使你不使用条件导入,宏展开在自动完成/预测方面的支持也往往较差,尤其是在处理非常复杂的生成类型时。这意味着虽然你的模拟在运行时没有开销,但在最重要的阶段——开发时,它却有很大的开销。
stubby
的工作原理以及为什么它更无缝
stubby
被设计用来避免条件导入和过程宏的陷阱。它通过将模拟行为存储为要模拟的结构体的属性来实现这一点,而不是创建一个全新的结构体。避免使用过程宏意味着稍微多一些样板代码,但得益于stub!
和stub_if_some!
,通常每个方法只需要一行代码。尽管stubby
在发布模式下编译时没有任何成本,通过将其状态替换为()
,但它仍然提供了完全相同的接口,以便让IDE更容易处理。
作为额外的好处,stubby
由于没有依赖项,仅使用声明式宏,且代码行数少于300行,因此编译速度更快!
然而,它只具有mockall
众多功能中的一个,因此如果您需要一个功能更完整的解决方案,请检查它!
类型安全
由于当前Rust编译器的限制,当前模拟类型无法进行类型检查。但是,如果您的项目使用Rust nightly,您可以选择启用type-safe
功能,这将消除对fn_name!
的需求,并允许您直接传递方法签名,并检查您的类型。使用cargo doc
本地生成文档,以查看完整的更新后的接口/文档