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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 12:43:09