引言:为什么 Rust 中的测试不仅是潮流,更是必需
\n\nRust 是一种以内存安全和高性能著称的语言。然而,即使是最严格的编译器也无法保证业务逻辑的正确性。这正是测试登场的时候。在 Rust 中,类型系统和所有权机制在编译阶段就排除了大量错误,但单元测试、集成测试和端到端(e2e)测试能确保你的代码确实按照你的预期运行。
\n\n在本文中,我们将探讨 Rust 中的三个测试层级:从独立的单元测试到完整的端到端场景。你将学习如何编写高效的测试、使用哪些工具以及如何组织测试基础设施。本文内容既适合刚开始学习 Rust 的新手,也适合希望系统化知识的有经验开发者。
\n\n1. 单元测试:基础中的基础
\n\n单元测试是最快速、最隔离的测试层级。它们测试独立的函数、方法或小型模块,与外部依赖隔离。在 Rust 中,单元测试通常放在与被测代码相同的文件中,位于 tests 模块内。
1.1. 结构与属性
\n\n创建单元测试使用 #[test] 属性。被标记的函数不接受参数且不返回任何值(或返回 Result)。如果函数内部发生恐慌(panic),则测试被视为失败。
// src/lib.rs\npub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n fn test_add() {\n assert_eq!(add(2, 2), 4);\n }\n\n #[test]\n fn test_add_negative() {\n assert_eq!(add(-1, -1), -2);\n }\n}\n\n请注意 #[cfg(test)]。这个属性确保包含测试的模块仅在运行测试时(cargo test)被编译。它不会包含在生产构建中。
1.2. 断言宏
\n\nRust 提供了几个用于检查断言的宏:
\n\n- \n
assert!(expr)— 检查表达式是否为真。 \n assert_eq!(left, right)— 检查相等性(需要PartialEq和Debug)。 \n assert_ne!(left, right)— 检查不相等性。 \n panic!("message")— 强制引发恐慌(用于测试错误情况)。 \n
1.3. 使用 should_panic 测试恐慌
\n\n如果你的函数在输入无效数据时应引发恐慌,请使用 #[should_panic] 属性:
pub fn divide(a: f64, b: f64) -> f64 {\n if b == 0.0 {\n panic!("Division by zero");\n }\n a / b\n}\n\n#[cfg(test)]\nmod tests {\n use super::*;\n\n #[test]\n #[should_panic(expected = "Division by zero")]\n fn test_divide_by_zero() {\n divide(10.0, 0.0);\n }\n}\n\nexpected 参数是可选的,但它能使测试更精确:恐慌消息必须包含指定的字符串。
2. 集成测试:检查模块间的交互
\n\n集成测试检查应用程序的不同部分如何协同工作。与单元测试不同,它们位于项目根目录下的单独 tests/ 目录中(与 src/ 同级)。此文件夹中的每个文件都作为独立的 crate 库进行编译。
2.1. 创建集成测试
\n\n创建文件 tests/integration_test.rs:
// tests/integration_test.rs\nuse my_crate::add; // 假设你的 crate 名为 my_crate\n\n#[test]\nfn test_add_integration() {\n let result = add(5, 10);\n assert_eq!(result, 15);\n}\n\n要仅运行集成测试