0
点赞
收藏
分享

微信扫一扫

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

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

实现特征

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

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 Trait​​​与​​Box<dyn Trait>​​​有所不同,后者需要的分配更少。同时前者比起后者有更多功能。比如说通过​​Sized​​特征无法建立另一个特征对象,所以以下代码将会出错:


struct Foo {}
#[automock]
impl Foo {
fn foo(&self) -> impl Clone {
// ...
}
}

创建一个实现超过两个非自动类型的特征对象也是不允许的。所以以下代码也会出错:

struct Foo {}
#[automock]
impl Foo {
fn foo(&self) -> impl Debug + Display {
// ...
}
}

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

模拟结构型

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

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

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​​,并且通用型寿命参数是不被允许的。

#[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​​。另一个限制是通用寿命不能显示为返回类型的一部分。还有,任何方法都不能同时有通用寿命参数或通用类型参数。

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​​,并且不能使用通用寿命参数。


#[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的介绍估计还有两期就可以结束了,希望对使用单元测试的朋友有所帮助。


举报

相关推荐

0 条评论