如何在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库自动生成模拟实现更高效:
- 先加dev依赖到Cargo.toml:
[dev-dependencies] mockall = "0.11"
- 给Trait加
#[automock]宏:
use mockall::automock; #[automock] trait DirWalker { type Iter: Iterator<Item = Result<DirEntry, Error>>; fn walk(&self) -> Self::Iter; }
- 测试时直接用自动生成的
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
相关产品推荐
相关产品推荐

