< 返回版块

0x5F3759DF 发表于 2020-06-11 21:03

Tags:mock,unit test

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

评论区

写评论

还没有评论

1 共 0 条评论, 1 页