Rust测试中如何Mock `std::process::Child`结构体?
解决Rust中Mock
std::process::Child 的方案 由于Rust的mock框架(如mockall、faux)无法直接Mock标准库的外部结构体,核心思路是通过抽象层+依赖注入隔离真实进程操作,让业务逻辑依赖自定义抽象而非直接依赖std::process类型。
步骤1:定义抽象Trait
先封装进程启动和子进程行为的抽象,只保留业务实际用到的方法:
use std::process::ExitStatus; use std::io::Result; // 抽象子进程的核心行为(按需扩展) trait ChildHandle { fn wait(&mut self) -> Result<ExitStatus>; fn kill(&mut self) -> Result<()>; // 如果业务需要操作/stdin/stdout/stderr,可添加对应方法 } // 抽象进程启动器 trait ProcessSpawner { fn spawn(&self, cmd: &str, args: &[&str]) -> Result<Box<dyn ChildHandle>>; }
步骤2:实现生产环境的真实逻辑
基于标准库实现上述Trait,供业务代码在生产环境使用:
// 包装std::process::Child的真实实现 struct RealChild(std::process::Child); impl ChildHandle for RealChild { fn wait(&mut self) -> Result<ExitStatus> { self.0.wait() } fn kill(&mut self) -> Result<()> { self.0.kill() } } // 真实的进程启动器 struct RealProcessSpawner; impl ProcessSpawner for RealProcessSpawner { fn spawn(&self, cmd: &str, args: &[&str]) -> Result<Box<dyn ChildHandle>> { let child = std::process::Command::new(cmd) .args(args) .spawn()?; Ok(Box::new(RealChild(child))) } }
步骤3:实现Mock版本(以mockall为例)
用mockall生成Trait的Mock实现,供测试使用:
use mockall::mock; mock! { pub MockChildHandle {} impl ChildHandle for MockChildHandle { fn wait(&mut self) -> Result<ExitStatus>; fn kill(&mut self) -> Result<()>; } } mock! { pub MockProcessSpawner {} impl ProcessSpawner for MockProcessSpawner { fn spawn(&self, cmd: &str, args: &[&str]) -> Result<Box<dyn ChildHandle>>; } }
步骤4:重构业务逻辑依赖抽象
让业务代码依赖ProcessSpawnerTrait,而非直接调用std::process::Command:
struct MyApp<S: ProcessSpawner> { spawner: S, } impl<S: ProcessSpawner> MyApp<S> { pub fn new(spawner: S) -> Self { Self { spawner } } // 业务逻辑:启动长期运行的进程 pub fn start_long_running_task(&self) -> Result<Box<dyn ChildHandle>> { self.spawner.spawn("my_long_running_cmd", &["--config", "prod"]) } }
步骤5:编写Mock测试
测试时注入Mock实现,完全避免启动真实进程:
#[cfg(test)] mod tests { use super::*; use mockall::predicate::eq; #[test] fn test_start_task_success() { // 初始化Mock实例 let mut mock_spawner = MockProcessSpawner::new(); let mut mock_child = MockChildHandle::new(); // 设定Mock的预期行为:调用指定命令时返回Mock子进程 mock_spawner .expect_spawn() .with(eq("my_long_running_cmd"), eq(&["--config", "prod"][..])) .returning(move |_, _| Ok(Box::new(mock_child))); // 注入Mock并执行业务逻辑 let app = MyApp::new(mock_spawner); let result = app.start_long_running_task(); // 验证结果 assert!(result.is_ok()); } #[test] fn test_child_wait_returns_success() { let mut mock_child = MockChildHandle::new(); // 设定wait方法返回成功状态 mock_child .expect_wait() .returning(|| Ok(ExitStatus::default())); let status = mock_child.wait(); assert!(status.is_ok()); } }
关键说明
- 只抽象业务实际用到的
Child方法,无需完整复刻标准库的所有API,减少冗余。 - 这种方式不仅能Mock进程操作,还能让业务逻辑更易扩展(比如后续替换为其他进程管理实现)。
内容的提问来源于stack exchange,提问作者Nino Martinez Wael
相关产品推荐
相关产品推荐

