2 个不稳定版本

0.2.0 2024 年 8 月 19 日
0.1.0 2023 年 6 月 27 日

#67测试

Download history 114/week @ 2024-08-16

114 每月下载量

MIT 许可证

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 SelfBox<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());
}

使用序列

默认情况下,期望不绑定到特定的顺序。只要所有定义的期望都使用正确的参数执行,一旦句柄被丢弃,就不会引发错误。要绑定期望到特定的顺序,您可以使用 SequenceInSequence

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 方法来完成。

如果您将 timesSequence 结合使用,则期望的调用次数需要在序列中的下一个期望被视为活动之前匹配预期的调用次数。

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