这次继续为大家讲解单元测试模拟接口的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的介绍估计还有两期就可以结束了,希望对使用单元测试的朋友有所帮助。