Unit testing 对另一个结构实现所使用的特性进行锈蚀模拟

Unit testing 对另一个结构实现所使用的特性进行锈蚀模拟,unit-testing,rust,Unit Testing,Rust,我严格地试图模拟std::path::PathBuf.is_dir方法,但我认为这里有一个更通用的用例,实际上是关于模拟外部特性 我已经创建了一个封装PathBuf.is\u dir方法的trait,从理论上讲,它应该能够让我模拟我的is\u dir封装 use mockall::*; use std::path::PathBuf; #[derive(Debug, Clone, PartialEq)] pub enum PackageFileIndexError { ArchiveRo

我严格地试图模拟
std::path::PathBuf.is_dir
方法,但我认为这里有一个更通用的用例,实际上是关于模拟外部特性

我已经创建了一个封装
PathBuf.is\u dir
方法的trait,从理论上讲,它应该能够让我模拟我的
is\u dir
封装

use mockall::*;
use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq)]
pub enum PackageFileIndexError {
    ArchiveRootNotADirectory,
}

#[automock]
trait PathInterface {
    // Encapsulate the is_dir method to make it mockable.
    fn is_dir(this_path: &PathBuf) -> bool {
        this_path.is_dir()
    }
}

pub struct PackageFileIndexData {
    archive_root_path: PathBuf,
}

impl PackageFileIndexData {
    pub fn new(archive_root: &str) -> Result<PackageFileIndexData, PackageFileIndexError> {
        let archive_root_path = PathBuf::from(archive_root.clone());

        if !Self::is_dir(&archive_root_path) {
            return Err(PackageFileIndexError::ArchiveRootNotADirectory);
        }

        Ok(PackageFileIndexData { archive_root_path })
    }
}

impl PathInterface for PackageFileIndexData {}

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

    mock! {
        PackageFileIndexData {}

        trait PathInterface {
            fn is_dir(this_path: &PathBuf) -> bool;
        }
    }

    #[test]
    fn test_bad_directory() {
        let ctx = MockPackageFileIndexData::is_dir_context();
        ctx.expect().times(1).returning(|_x| false);

        let result = PackageFileIndexData::new("bad_directory").err().unwrap();

        assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
    }

    #[test]
    fn test_good_directory() {
        let ctx = MockPackageFileIndexData::is_dir_context();
        ctx.expect().times(1).returning(|_x| true);

        let _result = PackageFileIndexData::new("good_directory").unwrap();
    }

    #[test]
    fn test_bad_directory2() {
        let ctx = MockPathInterface::is_dir_context();
        ctx.expect().times(1).returning(|_x| false);

        let result = PackageFileIndexData::new("bad_directory").err().unwrap();

        assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
    }

    #[test]
    fn test_good_directory2() {
        let ctx = MockPathInterface::is_dir_context();
        ctx.expect().times(1).returning(|_x| true);

        let _result = PackageFileIndexData::new("good_directory").unwrap();
    }
}
使用mockall::*;
使用std::path::PathBuf;
#[派生(调试、克隆、PartialEq)]
发布枚举包文件索引器{
Archiverootnota目录,
}
#[自动模拟]
特征路径接口{
//封装is_dir方法以使其可模拟。
fn是\u dir(此路径:&PathBuf)->bool{
这个路径是
}
}
发布结构包文件索引数据{
存档根路径:PathBuf,
}
impl PackageFileIndexData{
pub fn new(存档根目录:&str)->结果{
让archive_root_path=PathBuf::from(archive_root.clone());
if!Self::is_dir(&archive_root_path){
返回Err(PackageFileIndexer::ArchiverOotDirectory);
}
Ok(PackageFileIndexData{archive\u root\u path})
}
}
PackageFileIndexData{}的impl路径接口
#[cfg(测试)]
模试验{
使用超级::*;
嘲笑{
PackageFileIndexData{}
特征路径接口{
fn是_dir(此路径:&PathBuf)->bool;
}
}
#[测试]
fn测试错误目录(){
设ctx=MockPackageFileIndexData::is_dir_context();
ctx.expect().times(1).返回(|ux| false);
让result=PackageFileIndexData::new(“坏目录”).err().unwrap();
assert_eq!(结果,PackageFileIndexer::ArchiverOotDirectory);
}
#[测试]
fn测试良好目录(){
设ctx=MockPackageFileIndexData::is_dir_context();
ctx.expect().times(1).返回(|ux|true);
让_result=PackageFileIndexData::new(“good_目录”).unwrap();
}
#[测试]
fn测试错误目录2(){
设ctx=MockPathInterface::is_dir_context();
ctx.expect().times(1).返回(|ux| false);
让result=PackageFileIndexData::new(“坏目录”).err().unwrap();
assert_eq!(结果,PackageFileIndexer::ArchiverOotDirectory);
}
#[测试]
fn测试良好指导2(){
设ctx=MockPathInterface::is_dir_context();
ctx.expect().times(1).返回(|ux|true);
让_result=PackageFileIndexData::new(“good_目录”).unwrap();
}
}
所有这些测试都失败,如下所示。在我看来,可用的模拟(测试正在查找各种模拟上下文)没有被正在运行的测试使用

---- mock_is_dir::tests::test_good_directory1 stdout ----
thread 'mock_is_dir::tests::test_good_directory1' panicked at 'called `Result::unwrap()` on an `Err` value: ArchiveRootNotADirectory', src/mock_is_dir.rs:63:23

---- mock_is_dir::tests::test_bad_directory2 stdout ----
thread 'mock_is_dir::tests::test_bad_directory2' panicked at 'MockPathInterface::is_dir: Expectation(<anything>) called fewer than 1 times', src/mock_is_dir.rs:10:1

---- mock_is_dir::tests::test_good_directory2 stdout ----
thread 'mock_is_dir::tests::test_good_directory2' panicked at 'called `Result::unwrap()` on an `Err` value: ArchiveRootNotADirectory', src/mock_is_dir.rs:81:23

---- mock_is_dir::tests::test_bad_directory1 stdout ----
thread 'mock_is_dir::tests::test_bad_directory1' panicked at 'MockPackageFileIndexData::is_dir: Expectation(<anything>) called fewer than 1 times', src/mock_is_dir.rs:40:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    mock_is_dir::tests::test_bad_directory1
    mock_is_dir::tests::test_bad_directory2
    mock_is_dir::tests::test_good_directory1
    mock_is_dir::tests::test_good_directory2

test result: FAILED. 0 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out
----mock_是目录::tests::test_good_目录1 stdout----
线程'mock_is_dir::tests::test_good_directory1'在'Err'值:ArchiveRootNotADirectory'上调用'Result::unwrap()'时惊慌失措,src/mock_is_dir.rs:63:23
----模拟目录::测试::测试目录2标准目录----
线程“mock_is_dir::tests::test_bad_directory2”在“MockPathInterface::is_dir:Expectation()调用次数少于1次”时惊慌失措,src/mock_is_dir.rs:10:1
----模拟目录::测试::测试目录2标准目录----
线程'mock_is_dir::tests::test_good_directory2'在'Err'值:ArchiveRootNotADirectory'上调用'Result::unwrap()'时惊慌失措,src/mock_is_dir.rs:81:23
----模拟目录::测试::测试目录1标准目录----
线程“mock_is_dir::tests::test_bad_directory1”在“MockPackageFileIndexData::is_dir:Expectation()调用次数少于1次”时惊慌失措,src/mock_is_dir.rs:40:5
注意:使用'RUST_BACKTRACE=1'环境变量运行以显示回溯
失败:
mock_is_dir::tests::test_bad_directory1
mock_is_dir::tests::test_bad_directory2
模拟目录::测试::测试目录良好目录1
模拟目录::测试::测试目录良好目录2
测试结果:失败。0通过;失败4例;忽略0;0测量值;0被过滤掉

不幸的是,你所做的不会起作用,至少在你想要的方式上是这样
#[automock]
正在创建一个名为
MockPathInterface
struct
。然后,您可以将该类型传递到期望实现
路径接口的函数中。它不能改变实现该特性的现有结构的行为,至少不能直接改变。您确实在测试中正确设置了
MockPathInterface
,但是没有将其与
PackageFileInfoData
关联,因此它永远不会被使用

你可以这样做的一种方法是我修改你的代码来传递行为,然后你可以传递你的模拟行为。例如:

#[automock]
pub trait PathInterface {
    // Encapsulate the is_dir method to make it mockable.
    fn is_dir(this_path: &PathBuf) -> bool {
        this_path.is_dir()
    }
}

pub struct PackageFileIndexData {
    archive_root_path: PathBuf,
}

impl PackageFileIndexData {
    pub fn new<PI: PathInterface>(archive_root: &str) -> Result<PackageFileIndexData, PackageFileIndexError> {
        let archive_root_path = PathBuf::from(archive_root.clone());

        if !PI::is_dir(&archive_root_path) {
            return Err(PackageFileIndexError::ArchiveRootNotADirectory);
        }

        Ok(PackageFileIndexData { archive_root_path })
    }
}
这里唯一的变化是使用turbofish(
)操作符将类型传递到
新的
静态方法中


这些变化在美学上是丑陋的,我不一定建议你在任何地方都使用这种模式——如果你真的要嘲笑很多这样的行为,它基本上是无法维护的。但这说明了如何做到这一点,以及在锈蚀中进行模拟的局限性。

如果使用
cfg,那么当模拟混凝土结构时,Mockall正是打算使用的方式。

我将不得不考虑这是否更易于维护,但我想,使用通用构造函数建议的另一种方法是使用
cfg if
或类似方法进行宏级编译时控制。我认为泛型比使用宏更干净,但可维护性让我担心。如果模拟不能或不应该在这里使用,那么测试这种实现的惯用方法是什么?使用cfg-If模拟结构是一个很好的选择,尽管我以前没有使用cfg-If。mockall文档中有使用它来模拟结构的示例。我不认为这两种方法本身都是“错误的”,尽管模仿结构似乎更粗糙。我不喜欢模仿,除非我没有其他选择,但在我这样做的地方,我使用通用构造函数风格的方法
#[test]
fn test_bad_directory() {
    let ctx = MockPathInterface::is_dir_context();
    ctx.expect().times(1).returning(|_x| false);

    let result = PackageFileIndexData::new::<MockPathInterface>("bad_directory").err().unwrap();

    assert_eq!(result, PackageFileIndexError::ArchiveRootNotADirectory);
}