Rust 测试代码应该放哪里?

Rust 测试代码应该放哪里?单元 vs 集成的全面实战指南

Rust 的测试系统功能强大,官方推荐的测试方式也非常灵活。但很多初学者和团队在项目发展到一定规模时都会遇到这样的疑问:

为什么 Rust 的单元测试常常写在实现代码的同一个文件中?这样不会导致文件很臃肿吗?
如果我们模块比较大,有多层子模块,该如何组织测试代码更利于维护?

这篇文章将用通俗易懂的方式,带你深入理解 Rust 的测试组织原则、模块结构的最佳实践,并结合实际案例介绍企业级项目中如何拆分测试与模块结构。


Rust 中的两类测试

类型 文件位置 适合场景 是否可访问私有函数
单元测试 实现代码同一个文件或模块内 单个函数/模块的细节测试 ✅ 可以访问私有项
集成测试 tests/ 目录,完全独立的文件 模块之间的协作 & 对外接口 ❌ 只能访问 pub 接口

✅ 单元测试:靠近代码,快速反馈

最常见、推荐的写法是在实现代码下方写一个内联测试模块:

fn square(x: i32) -> i32 {
    x * x
}

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

    #[test]
    fn test_square() {
        assert_eq!(square(3), 9);
    }
}

优势

  • 测试可以访问私有函数
  • 开发效率高,修改函数后可立刻验证
  • 使用 #[cfg(test)] 控制,发布时不会被编译入最终产物

项目模块结构越来越大怎么办?

当模块变多、测试增多时,将所有测试写在同一个文件会越来越混乱。这时就该使用模块划分 + 文件分离策略


业内推荐的模块结构与测试拆分方式

假设我们有一个业务模块 foo,它有多个逻辑组件:

src/
├── lib.rs
└── foo/
    ├── mod.rs          // 声明模块
    ├── model.rs        // 业务逻辑:数据结构等
    ├── service.rs      // 实际业务处理逻辑
    └── tests.rs        // 单元测试:可测试 model + service

mod.rs 作为统一入口:

// src/foo/mod.rs
pub mod model;
pub mod service;

#[cfg(test)]
mod tests;

model.rs 中有内部逻辑:

// src/foo/model.rs
#[derive(Debug)]
pub struct User {
    pub id: u32,
    pub name: String,
}

fn parse_name(raw: &str) -> String {
    raw.trim().to_string()
}

tests.rs 中测试私有逻辑:

// src/foo/tests.rs
use super::model::*;
use super::service::*;

#[test]
fn test_parse_name() {
    assert_eq!(parse_name(" Tom "), "Tom");
}

#[test]
fn test_user_creation() {
    let user = User { id: 1, name: "Alice".to_string() };
    assert_eq!(user.name, "Alice");
}

✅ 这样组织结构的优势:

  • 清晰职责划分:实现和测试分文件管理
  • 测试仍然可访问私有函数(因为在同模块中)
  • 更适合大型项目扩展:后续可以按 model/service 拆多个子模块

多层子模块的测试如何组织?

以如下结构为例:

src/
└── auth/
    ├── mod.rs              // 声明模块
    ├── domain/
    │   ├── mod.rs
    │   ├── model.rs
    │   └── tests.rs
    └── logic/
        ├── mod.rs
        ├── login.rs
        └── tests.rs

domain/mod.rs:

pub mod model;

#[cfg(test)]
mod tests;

✅ 多层模块的测试也写在子模块下的 tests.rs 中,方便聚焦某一层的逻辑测试。


集成测试:从用户角度验证行为

project-root/
├── src/
│   └── lib.rs
├── tests/
│   ├── auth_api.rs
│   └── user_flow.rs

集成测试就是在 crate 外部重新导入你库中的接口:

// tests/auth_api.rs
use your_crate::auth::login;

#[test]
fn test_login_success() {
    let result = login("alice", "password123");
    assert!(result.is_ok());
}

✅ 特点:

  • 类似黑盒测试,从用户角度验证功能是否可用
  • 无法访问内部私有逻辑
  • 每个文件是独立 crate,可测试多个 crate 组合行为

✅ 总结推荐实践

场景 测试写在哪? 原因
小模块、逻辑简单 实现文件底部的 mod tests 快速反馈
模块较大,多个逻辑文件 拆出 tests.rs 放入模块同级 清晰职责,仍可访问私有
项目结构复杂,多级目录结构 每个子模块下都建自己的 tests.rs 局部测试、易扩展
用户行为、模块协作、对外接口验证 项目根目录的 tests/ 黑盒视角,模拟实际使用者

最后总结口诀

小而精,本地测;大而全,拆模块;对外测,放 tests!

你可能感兴趣的:(rust,rust)