2 个不稳定版本
新 0.2.0 | 2024 年 8 月 19 日 |
---|---|
0.1.0 | 2023 年 6 月 27 日 |
#67 在 测试
114 每月下载量
64KB
1.5K SLoC
murf
是一个受 Google 实现的 gmock 框架启发的 Rust 的模拟和单元测试框架。
murf
目前处于开发阶段,并被 peeriot 组织的内部项目广泛使用。我们认为 murf
可能对其他开发者也有用,因此我们决定将其开源。我们相信它对 RUST 开发者很有帮助。
如果这个解决方案吸引了你,我们当然非常乐意你为项目做出贡献。
我们也很感激对解决方案的任何反馈。它帮助我们改进这个包。
我们期待您的反馈 :)
功能
murf
有很多功能。为了获得更好的概述,以下是最重要的几个:
murf
使用 proc 预处理器宏来生成您的特性和类型的模拟版本。这使得它非常容易使用。murf
只是一个开发依赖项。这样可以保持您的生产代码干净。murf
使用Matcher
(用于检查预期函数调用的参数)和Action
(为预期函数调用执行的操作)特性(如 gmock 中所知),您可以用来实现自定义行为。这使得它非常容易扩展。murf
使用一个句柄,您可以使用它来添加更多期望,同时实际的模拟对象已经被传递到测试代码中。这使得它更加灵活。murf
能够处理局部引用作为函数参数以及返回值。murf
可以处理不同的self
参数和返回类型(例如&Self
、&mut Self
、Box<Self>
、Pin<&mut Self>
、Arc<Self>
以及更多)murf
支持泛型特性和关联类型murf
支持模拟方法的默认行为murf
能够按照定义的顺序处理期望murf
支持检查点以在指定点验证所有期望murf
能够处理定义期望的调用次数(支持范围)murf
也支持模拟关联函数(因此你可以模拟构造函数,例如MyTrait::new()
)
如何使用
以下部分将给出如何在你的环境中使用 murf
创建模拟对象的简单代码示例。有关更详细的示例,请查看 tests
目录。对于 murf
支持的每个功能,我们至少有一个示例来展示如何使用它。
简单示例
以下示例显示了一个使用特性行执行某些代码的服务。然后使用 murf
模拟该特性,并将其传递给服务而不是实际实现。因此,服务代码可以针对特性进行测试。
use murf::{mock, expect_method_call, matcher::eq, action::Return};
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, x: usize) -> usize;
}
/// A service that uses [`MyTrait`]
struct Service<T: MyTrait> {
inner: T,
}
impl<T: MyTrait> Service<T> {
fn new(inner: T) -> Self {
Self { inner }
}
fn exec(&self) -> usize {
self.inner.exec(4)
}
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, x: usize) -> usize;
}
}
fn main() {
let mock = MyStruct::mock();
expect_method_call!(mock as MyTrait, exec(eq(4))).will_once(Return(104));
let service = Service::new(mock);
assert_eq!(104, service.exec());
}
使用句柄
在模拟对象传递给待测试代码之前定义所有期望之前,您可以使用所谓的句柄来控制和操纵模拟对象。
use murf::{mock, expect_method_call, matcher::eq, action::Return};
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, x: usize) -> usize;
}
/// A service that uses [`MyTrait`]
struct Service<T: MyTrait> {
inner: T,
}
impl<T: MyTrait> Service<T> {
fn new(inner: T) -> Self {
Self { inner }
}
fn exec(&self) -> usize {
self.inner.exec(4)
}
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, x: usize) -> usize;
}
}
fn main() {
let (handle, mock) = MyStruct::mock_with_handle();
// Move the mocked object to the service
let service = Service::new(mock);
// Use the handle to control the mocked object
expect_method_call!(handle as MyTrait, exec(eq(4))).will_once(Return(104));
expect_method_call!(handle as MyTrait, exec(eq(4))).will_once(Return(105));
assert_eq!(104, service.exec());
assert_eq!(105, service.exec());
handle.checkpoint();
expect_method_call!(handle as MyTrait, exec(eq(4))).will_once(Return(106));
assert_eq!(106, service.exec());
}
使用序列
默认情况下,期望不绑定到特定的顺序。只要所有定义的期望都使用正确的参数执行,一旦句柄被丢弃,就不会引发错误。要绑定期望到特定的顺序,您可以使用 Sequence
或 InSequence
。
use murf::{mock, expect_method_call, InSequence, action::Return};
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, x: usize) -> usize;
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, x: usize) -> usize;
}
}
fn main() {
let seq = InSequence::default();
let mock = MyStruct::mock();
expect_method_call!(mock as MyTrait, exec(_)).will_once(Return(4));
expect_method_call!(mock as MyTrait, exec(_)).will_once(Return(5));
expect_method_call!(mock as MyTrait, exec(_)).will_once(Return(6));
assert_eq!(4, mock.exec(1));
assert_eq!(5, mock.exec(2));
assert_eq!(6, mock.exec(3));
}
使用调用次数
有时,限制期望的预期调用次数也可能很有趣。这可以通过使用期望构建器的 times
方法来完成。
如果您将 times
与 Sequence
结合使用,则期望的调用次数需要在序列中的下一个期望被视为活动之前匹配预期的调用次数。
use murf::{mock, expect_method_call, InSequence};
use murf::matcher::eq;
use murf::action::Return;
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, x: usize) -> usize;
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, x: usize) -> usize;
}
}
fn main() {
let seq = InSequence::default();
let mock = MyStruct::mock();
expect_method_call!(mock as MyTrait, exec(eq(1)))
.times(..2) // 0-1 times
.will_repeatedly(Return(4));
expect_method_call!(mock as MyTrait, exec(eq(2)))
.times(1..) // at least one time
.will_repeatedly(Return(5));
expect_method_call!(mock as MyTrait, exec(eq(3)))
.times(2..) // at least two times
.will_repeatedly(Return(6));
assert_eq!(5, mock.exec(2));
assert_eq!(6, mock.exec(3));
assert_eq!(6, mock.exec(3));
}
使用匹配器
要指定对调用期望的参数,可以使用所谓的 Matcher
。如果您对验证某个特定参数不感兴趣,可以使用 any
匹配器或简单地使用 _
在 expect_method_call!
宏中。
use murf::{mock, expect_method_call};
use murf::matcher::{str_starts_with, eq};
use murf::action::Return;
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, a: usize, b: &str, c: usize);
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, a: usize, b: &str, c: usize);
}
}
fn main() {
let mock = MyStruct::mock();
expect_method_call!(mock as MyTrait, exec(eq(1), str_starts_with("Hello"), _));
mock.exec(1, "Hello World :)", 1234);
}
嵌套匹配器
匹配器也可以嵌套。这对于例如在将参数传递给实际匹配器之前操纵参数很有用。
use std::ops::Deref;
use murf::{mock, expect_method_call};
use murf::matcher::{deref, eq};
use murf::action::Return;
/// Simple trait that executes something once `exec` is called.
trait MyTrait {
fn exec(&self, a: Wrapper);
}
struct Wrapper(usize);
impl Deref for Wrapper {
type Target = usize;
fn deref(&self) -> &Self::Target {
&self.0
}
}
mock! {
#[derive(Default)]
pub struct MyStruct;
impl MyTrait for MyStruct {
fn exec(&self, a: Wrapper);
}
}
fn main() {
let mock = MyStruct::mock();
// Using `eq` directly would cause an error.
// expect_method_call!(mock as MyTrait, exec(eq(1)));
expect_method_call!(mock as MyTrait, exec(deref(eq(1))));
mock.exec(Wrapper(1));
}
与其他crate的比较
murf
不是唯一的模拟和单元测试框架,但 murf
是唯一一个结合了所有其他crate最佳功能的框架。
v
完全支持-
部分支持x
不支持?
未知
特性 | murf |
mockall |
mockers |
mock_derive |
galvanic_mock |
pseudo |
faux |
unimock |
mry |
---|---|---|---|---|---|---|---|---|---|
维护 | v | v | - | x | x | x | - | v | - |
文档 | v | v | v | - | - | - | v | v | - |
proc宏 | v | v | v | v | v | x | v | v | v |
仅开发依赖 | v | v | - | - | - | v | - | - | - |
匹配器/动作接口 | v | v | x | x | - | - | v | v | - |
拆分为句柄和模拟对象 | v | x | v | x | x | x | x | x | x |
支持本地引用 | v | v | x | ? | ? | x | - | x | ? |
支持Self 类型 |
v | v | v | ? | ? | v | ? | v | ? |
支持泛型特质 | v | v | v | v | v | - | x | - | - |
支持关联类型 | v | v | - | ? | - | - | x | - | - |
支持关联函数 | v | v | x | ? | x | - | x | x | v |
支持默认动作 | v | x | x | v | x | - | x | x | v |
按顺序定义期望 | v | v | v | ? | x | x | x | - | x |
支持检查点 | v | v | v | ? | x | - | x | x | x |
定义期望的调用次数 | v | v | v | x | v | - | v | v | v |
许可证
本项目受MIT许可证许可。
依赖
~5–12MB
~133K SLoC