引言:为什么在 Rust 中进行测试是可靠代码的基础
Rust 是一种以内存安全性和高性能著称的系统编程语言。然而,即使是最严格的编译器也无法保证你的程序完全按照预期运行。这就是测试发挥作用的地方。在 Rust 中,内置的测试支持是现代编程语言中最好的之一。你可以在无需第三方库的情况下编写单元测试、集成测试和端到端测试(e2e)。
在本文中,我们将通过实际示例详细讲解所有三个层次的测试。你将学习如何编写测试、如何使用 cargo test 运行测试,以及如何为大型项目组织测试基础设施。
1. Rust 中的单元测试:测试函数和模块
单元测试用于验证单个函数或方法在隔离环境下的运行情况。在 Rust 中,通常将单元测试直接放在源代码文件中,放在带有 #[cfg(test)] 属性的 tests 模块内。这样测试就可以访问模块的私有函数。
1.1. 你的第一个单元测试
让我们创建一个简单的计算库,并为其编写测试。
// src/lib.rspub fn add(a: i32, b: i32) -> i32 { a + b}
pub fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("Division by zero")) } else { Ok(a / b) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_add() { assert_eq!(add(2, 3), 5); }
#[test] fn test_divide_success() { let result = divide(10.0, 2.0).unwrap(); assert_eq!(result, 5.0); }
#[test] fn test_divide_by_zero() { let result = divide(10.0, 0.0); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "Division by zero"); }}使用命令 cargo test 运行测试。如果所有测试通过,你将看到绿色的勾选标记。
1.2. 测试属性
#[test]— 表示该函数是一个测试。#[should_panic]— 如果函数发生恐慌(panic),则认为测试通过。#[ignore]— 临时禁用该测试。
#[test]#[should_panic(expected = "panic message")]fn test_panic() { panic!("panic message");}
#[test]#[ignore = "Test disabled until bug #42 is fixed"]fn test_expensive() { // Long-running test}2. 集成测试:检查组件交互
集成测试验证多个模块或外部依赖项一起工作的效果。在 Rust 中,它们被放置在项目根目录下单独的 tests/ 目录中。该文件夹中的每个文件都是一个独立的测试 crate,只能看到你库的公共 API。
2.1. 集成测试的结构
创建一个 tests/ 文件夹和一个文件 tests/integration_test.rs:
// tests/integration_test.rsuse my_crate::add; // Your crate's name
#[test]fn test_add_integration() { // Check that the function works as part of the library let result = add(10, 20); assert_eq!(result, 30);}
#[test]fn test_divide_integration() { // Test with multiple calls let a = 100.0; let b = 4.0; let result = my_crate::divide(a, b).unwrap(); assert!(result > 0.0); assert_eq!(result, 25.0);}2.2. 集成测试的共享模块
如果需要在集成测试之间复用代码,可以创建一个文件 tests/common/mod.rs。这不会被当作测试文件处理,但你可以从其他测试中导入它。
// tests/common/mod.rspub fn setu