前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【每周一库】- mockall 对象模拟库(第三部分)

【每周一库】- mockall 对象模拟库(第三部分)

作者头像
MikeLoveRust
发布2020-06-18 17:09:13
5800
发布2020-06-18 17:09:13
举报
文章被收录于专栏:Rust语言学习交流

这次继续为大家讲解单元测试模拟接口的Mockall其他的功能。

实现特征

Rust在1.26.0版本中引入了impl Trait功能,这样函数就可以返回未命名的具体类型(或者允许函数使用这样的类型作为参数)。这几乎Box<dyn Trait>相同,只是没有额外的分配。Mockall支持为返回impl Trait的方法生成mock,但是会有一些限制:Mockall内部会将期待的返回类型转换为Box<dyn Trait>,而不会改变mock方法的签名。所以你可以这样使用:

代码语言:javascript
复制
struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Debug {
        // ...
    }
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .returning(|| Box::new(String::from("Hello, World!")));
println!("{:?}", mock.foo());

但是需要注意的是impl TraitBox<dyn Trait>有所不同,后者需要的分配更少。同时前者比起后者有更多功能。比如说通过Sized特征无法建立另一个特征对象,所以以下代码将会出错:

代码语言:javascript
复制
struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Clone {
        // ...
    }
}创建一个实现超过两个非自动类型的特征对象也是不允许的。所以以下代码也会出错:
代码语言:javascript
复制
struct Foo {}
#[automock]
impl Foo {
    fn foo(&self) -> impl Debug + Display {
        // ...
    }
}

这些情况没有一劳永逸的解决方法。模拟这类方法最好的方式就是将方法重构成返回带命名的类型。

模拟结构型

Mockall既可以模拟特征,也可以模拟结构型。但是会有一个命名空间的问题:测试你的代码的时候很难提供模拟对象,因为这些模拟对象的命名会不同。解决的方法是在测试是改变引用路径。cfg-if包可以提供帮助。

#[automock\] 可以用于有一 impl 代码块的结构型:

代码语言:javascript
复制
mod thing {
    use mockall::automock;
    pub struct Thing{}
    #[automock]
    impl Thing {
        pub fn foo(&self) -> u32 {
            // ...
        }
    }
}

cfg_if! {
    if #[cfg(test)] {
        use self::thing::MockThing as Thing;
    } else {
        use self::thing::Thing;
    }
}

fn do_stuff(thing: &Thing) -> u32 {
    thing.foo()
}

#[cfg(test)]
mod t {
    use super::*;

    #[test]
    fn test_foo() {
        let mut mock = Thing::default();
        mock.expect_foo().returning(|| 42);
        do_stuff(&mock);
    }
}

对于那些有超过一个impl代码块的结构型,详情请看:mock!

通用方法

通用方法也可以模拟。每个通用方法其实相当于一个包含无限个普通方法的集合,其中的每一个方法就和任何一个普通方法一样。expect_*方法也为通用方法,通常需要利用turbofish调用。模拟通用方法唯一的限制是所有通用参数必须为'static,并且通用型寿命参数是不被允许的。

代码语言:javascript
复制
#[automock]
trait Foo {
    fn foo<T: 'static>(&self, t: T) -> i32;
}

let mut mock = MockFoo::new();
mock.expect_foo::<i16>()
    .returning(|t| i32::from(t));
mock.expect_foo::<i8>()
    .returning(|t| -i32::from(t));

assert_eq!(5, mock.foo(5i16));
assert_eq!(-5, mock.foo(5i8));

通用寿命的方法

带有寿命参数的方法严格意义上讲就是通用方法,但是Mockall会将这样的方法以可适用于所有寿命的非通用方法来对待。模拟这类方法与模拟非通用方法类似,但有一些额外的限制。其中一个限制是不能用with来匹配调用,而需要用withf。另一个限制是通用寿命不能显示为返回类型的一部分。还有,任何方法都不能同时有通用寿命参数或通用类型参数。

代码语言:javascript
复制
struct X<'a>(&'a i32);

#[automock]
trait Foo {
    fn foo<'a>(&self, x: X<'a>) -> i32;
}

let mut mock = MockFoo::new();
mock.expect_foo()
    .withf(|f| *f.0 == 5)
    .return_const(42);
let x = X(&5);
assert_eq!(42, mock.foo(x));

通用特征和结构型

模拟通用结构型和通用特征也不是问题。模拟出的结构型也会是通用的。限制与模拟通用方法一样:每个通用参数都必须是'static,并且不能使用通用寿命参数。

代码语言:javascript
复制

#[automock]
trait Foo<T: 'static> {
    fn foo(&self, t: T) -> i32;
}

let mut mock = MockFoo::<i16>::new();
mock.expect_foo()
    .returning(|t| i32::from(t));
assert_eq!(5, mock.foo(5i16));

Mockall的介绍估计还有两期就可以结束了,希望对使用单元测试的朋友有所帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现特征
  • 模拟结构型
  • 通用方法
  • 通用寿命的方法
  • 通用特征和结构型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档