Rust 高级测试:单元测试、集成测试、端到端测试

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Rust 高级测试:从单元测试到端到端测试

测试是使用 Rust 开发可靠系统软件不可或缺的一部分。如果基本概念(使用 #[test] 编写简单测试)对每个人来说都很熟悉,那么高级技术——如使用模拟、参数化、自定义属性以及组织端到端测试——则需要单独讲解。在本文中,我们将探讨如何使用单元测试、集成测试和端到端测试方法,将 Rust 测试提升到专业水平。



1. 单元测试:模拟、参数化和自定义属性

Rust 中的单元测试通常与代码写在同一个文件中,位于 tests 模块内。然而,在高级层面,需要将被测试的代码与外部依赖隔离开来。



使用 trait 创建模拟对象

在 Rust 中,创建模拟对象最常用的方法是使用 trait。您定义一个 trait,然后为真实对象和测试模拟对象分别实现它。流行的 crate 包括:mockall 和 mockito(用于 HTTP)。

use mockall::automock;

#[automock]trait Database { fn get_user(&self, id: u32) -> String;}

fn process_user(db: &dyn Database, id: u32) -> String { let user = db.get_user(id); format!("User: {}", user)}

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

#[test] fn test_process_user() { let mut mock = MockDatabase::new(); mock.expect_get_user() .with(predicate::eq(42)) .returning(|_| "Alice".to_string()); assert_eq!(process_user(&mock, 42), "User: Alice"); }}


参数化测试

使用不同输入数据重复执行同一个测试是一项常见任务。rstest crate 提供了一个优雅的解决方案。

use rstest::rstest;

#[rstest]#[case(2, 3, 5)]#[case(0, 0, 0)]#[case(-1, 1, 0)]fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) { assert_eq!(a + b, expected);}


自定义测试属性

有时需要在测试之前或之后执行逻辑。您可以通过过程宏创建自己的属性,但更简单的方法是使用 test-case 或 ctor crate 进行初始化。

// 使用 ctor 初始化日志记录器的示例#[cfg(test)]mod tests {    use ctor::ctor;

#[ctor] fn init() { // 在所有测试之前初始化一次 }}


2. 集成测试:组织与外部依赖协作

Rust 中的集成测试位于项目根目录的 tests/ 文件夹中。该文件夹中的每个文件都是一个独立的 crate。高级技巧包括共享模块以及使用真实数据库或 API 进行测试。



集成测试的共享工具

为了避免代码重复(例如,设置测试服务器),请在 tests/ 内创建一个 common 模块。

// tests/common/mod.rspub fn setup_test_db() -> String {    // 设置测试数据库    "connected".to_string()}

// tests/integration_test.rsmod common;

#[test]fn test_db_connection() { let db = common::setup_test_db(); assert_eq!(db, "connected");}


处理 HTTP 请求

要测试 HTTP 客户端,请使用 mockito——它会启动一个本地模拟服务器。

use mockito::{mock, server_url};

#[tokio::test]async fn test_api_call() { let _m = mock("GET", "/users/1") .with_status(200) .with_body(r#"{"id":1,"name":"Alice"}"#) .create();

let client = reqwest::Client::new(); let resp = client.get(&format!("{}/users/1", server_url())) .send() .await .unwrap(); assert!(resp.status().is_success());}


3. 端到端测试:应用程序的完整生命周期

端到端测试检查整个系统的运行情况:从启

博客

书籍推荐