You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rust中模拟外部依赖walkdir::WalkDir以测试逻辑?

模拟walkdir依赖做单元测试的方案

要测试foo方法里对遍历结果的处理逻辑,核心是把WalkDir的依赖抽象出来——用Trait隔离真实的遍历行为,这样测试时就能用模拟实现替换,完全不用碰真实文件系统。

步骤1:抽象遍历行为的Trait

先定义一个Trait,把目录遍历的核心行为封装起来,和WalkDir的迭代输出对齐:

use std::path::Path;
use walkdir::{DirEntry, Error};

// 抽象目录遍历的能力
trait DirWalker {
    type Iter: Iterator<Item = Result<DirEntry, Error>>;

    fn walk(&self) -> Self::Iter;
}

步骤2:改造MyStruct依赖Trait

调整MyStruct,让它持有一个DirWalker实例(构造注入的方式最灵活,方便测试替换):

pub struct MyStruct<'a, W: DirWalker> {
    pub walker: W,
    pub path: &'a Path,
}

impl<'a, W: DirWalker> MyStruct<'a, W> {
    pub fn new(path: &'a Path, walker: W) -> Self {
        Self { path, walker }
    }

    pub fn foo(&self) -> Vec<&str> {
        let walk = self.walker.walk();

        // 这里写你的实际处理逻辑,比如提取文件名示例
        walk.filter_map(|entry| {
            entry.ok().and_then(|e| e.file_name().to_str())
        }).collect()
    }
}

步骤3:让WalkDir适配Trait

给真实的WalkDir实现这个Trait,生产环境直接用:

use walkdir::WalkDir;

impl<'a> DirWalker for WalkDir<'a> {
    type Iter = walkdir::IntoIter;

    fn walk(&self) -> Self::Iter {
        self.into_iter()
    }
}

// 给生产环境加个快捷创建方法
impl<'a> MyStruct<'a, WalkDir<'a>> {
    pub fn with_real_walker(path: &'a Path) -> Self {
        Self::new(path, WalkDir::new(path))
    }
}

步骤4:写模拟遍历器做测试

自己写个模拟的DirWalker实现,返回预设的结果,测试时完全可控:

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

    // 模拟遍历器,返回预设的条目
    struct MockDirWalker {
        entries: Vec<Result<DirEntry, Error>>,
    }

    impl DirWalker for MockDirWalker {
        type Iter = std::vec::IntoIter<Result<DirEntry, Error>>;

        fn walk(&self) -> Self::Iter {
            self.entries.clone().into_iter()
        }
    }

    // 辅助方法:创建模拟用的DirEntry(用真实文件路径生成,测试里可以用临时文件或者提前准备的测试文件)
    fn mock_dir_entry(path: &str) -> DirEntry {
        let path = Path::new(path);
        let file = std::fs::File::open(path).unwrap();
        let meta = file.metadata().unwrap();
        DirEntry::new(path.to_path_buf(), meta, 0, 0).unwrap()
    }

    #[test]
    fn test_foo_handles_entries() {
        // 预设测试用的遍历结果:两个正常文件,一个错误
        let mock_entries = vec![
            Ok(mock_dir_entry("./test_files/file1.txt")),
            Ok(mock_dir_entry("./test_files/subdir/file2.txt")),
            Err(Error::from(std::io::Error::new(std::io::ErrorKind::PermissionDenied, "无权限"))),
        ];

        let mock_walker = MockDirWalker { entries: mock_entries };
        let my_struct = MyStruct::new(Path::new("./test_files"), mock_walker);

        let result = my_struct.foo();

        // 验证结果:只保留正常的文件名
        assert_eq!(result, vec!["file1.txt", "file2.txt"]);
    }
}

偷懒方案:用mockall自动生成模拟

不想手动写模拟代码的话,用mockall库自动生成模拟实现更高效:

  1. 先加dev依赖到Cargo.toml:
[dev-dependencies]
mockall = "0.11"
  1. 给Trait加#[automock]宏:
use mockall::automock;

#[automock]
trait DirWalker {
    type Iter: Iterator<Item = Result<DirEntry, Error>>;

    fn walk(&self) -> Self::Iter;
}
  1. 测试时直接用自动生成的MockDirWalker:
#[test]
fn test_foo_with_mockall() {
    let mut mock_walker = MockDirWalker::new();
    // 设置mock的返回值
    mock_walker.expect_walk().return_once(|| {
        vec![
            Ok(mock_dir_entry("./test/a.txt")),
            Ok(mock_dir_entry("./test/b.txt")),
        ].into_iter()
    });

    let my_struct = MyStruct::new(Path::new("./test"), mock_walker);
    let result = my_struct.foo();

    assert_eq!(result, vec!["a.txt", "b.txt"]);
}

内容的提问来源于stack exchange,提问作者WhatAmIDoing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 01:47:16