第12章 测试编写

文章目录
- 第12章 测试编写
- 12.1 测试函数编写
- Rust测试框架基础
- 基本测试结构
- 运行测试
- 断言宏详解
- 测试错误和panic
- 测试组织和模块化
- 12.2 组织集成测试
- 集成测试基础
- 项目结构
- 基本的集成测试
- 共享测试代码
- 复杂的集成测试场景
- 12.3 测试驱动开发实践
- TDD循环:红-绿-重构
- TDD基础示例:栈实现
- 实战TDD:购物车系统
- 第一阶段:基本购物车功能
- 第二阶段:价格计算
- 第三阶段:高级功能
- TDD最佳实践
- 1. 小步前进
- 2. 测试边界条件
- 3. 测试驱动设计
- 12.4 性能测试与基准测试
- 内置基准测试
- 使用Criterion.rs进行基准测试
- 性能回归测试
- 内存使用测试
- 并发性能测试
- 测试最佳实践总结
- 1. 测试组织结构
- 2. 测试命名约定
- 3. 测试隔离
- 4. 全面的测试覆盖
第12章 测试编写
测试是软件开发中不可或缺的重要环节,它确保代码的正确性、可靠性和可维护性。Rust语言内置了一个强大而灵活的测试框架,使得编写和运行测试变得异常简单。本章将全面深入地探讨Rust测试的各个方面,从基础的测试函数编写到复杂的集成测试组织,从测试驱动开发实践到性能测试与基准测试。
12.1 测试函数编写
Rust测试框架基础
Rust的测试框架设计哲学是"零配置"——只需简单的注解和约定,就能开始编写测试。测试代码通常与生产代码放在同一个文件中,但通过条件编译确保只在测试时包含。
基本测试结构
// 这是一个简单的函数,我们想要测试它
pub fn add(a: i32, b: i32) -> i32 {a + b
}// 测试模块,使用#[cfg(test)]确保只在测试时编译
#[cfg(test)]
mod tests {// 导入父模块的所有内容use super::*;// 基本的测试函数#[test]fn test_add() {assert_eq!(add(2, 2), 4);}// 测试失败的情况#[test]fn test_add_negative() {assert_eq!(add(-2, -3), -5);}// 测试边界情况#[test]fn test_add_zero() {assert_eq!(add(0, 0), 0);assert_eq!(add(5, 0), 5);assert_eq!(add(0, 5), 5);}
}
运行测试
使用Cargo运行测试非常简单:
# 运行所有测试
cargo test# 运行特定测试
cargo test test_add# 运行测试并显示输出(即使测试通过)
cargo test -- --nocapture# 多线程运行测试
cargo test -- --test-threads=1
断言宏详解
Rust提供了一系列强大的断言宏,用于验证测试条件:
#[cfg(test)]
mod assertion_tests {#[test]fn test_basic_assertions() {let value = 42;// assert! - 检查条件是否为trueassert!(value > 0);assert!(value.is_positive());// assert_eq! - 检查两个值是否相等assert_eq!(value, 42);assert_eq!(2 + 2, 4);// assert_ne! - 检查两个值是否不相等assert_ne!(value, 0);assert_ne!("hello", "world");// 自定义失败消息assert!(value > 100, "Value {} is not greater than 100", value);}#[test]fn test_float_assertions() {// 浮点数比较需要特殊处理let a = 0.1 + 0.2;let b = 0.3;// 使用近似相等比较浮点数assert!((a - b).abs() < f64::EPSILON);// 或者使用approx_eq crate进行更精确的浮点数比较}#[test]fn test_collection_assertions() {let vec = vec![1, 2, 3, 4, 5];// 检查集合包含元素assert!(vec.contains(&3));// 检查集合长度assert_eq!(vec.len(), 5);// 检查集合是否为空assert!(!vec.is_empty());let empty_vec: Vec<i32> = vec![];assert!(empty_vec.is_empty());}
}
测试错误和panic
测试不仅需要验证正常情况,还需要验证错误处理:
pub struct BankAccount {balance: i32,
}impl BankAccount {pub fn new(initial_balance: i32) -> Self {BankAccount { balance: initial_balance }}pub fn withdraw(&mut self, amount: i32) -> Result<i32, String> {if amount <= 0 {return Err("Amount must be positive".to_string());}if amount > self.balance {return Err("Insufficient funds".to_string());}self.balance -= amount;Ok(self.balance)}// 这个方法在特定条件下会panicpub fn transfer_all(&mut self) -> i32 {if self.balance == 0 {panic!("Cannot transfer from empty account");}let amount = self.balance;self.balance = 0;amount}
}#[cfg(test)]
mod error_tests {use super::*;#[test]fn test_successful_withdrawal() {let mut account = BankAccount::new(100);let result = account.withdraw(50);assert!(result.is_ok());assert_eq!(result.unwrap(), 50);}#[test]fn test_insufficient_funds() {let mut account = BankAccount::new(25);let result = account.withdraw(50);assert!(result.is_err());assert_eq!(result.unwrap_err(), "Insufficient funds");}#[test]fn test_invalid_amount() {let mut account = BankAccount::new(100);let result = account.withdraw(-10);assert!(result.is_err());assert_eq!(result.unwrap_err(), "Amount must be positive");}// 测试期望的panic#[test]#[should_panic(expected = "Cannot transfer from empty account")]fn test_transfer_from_empty_account() {let mut account = BankAccount::new(0);account.transfer_all();}// 使用Result返回错误而不是panic#[test]fn test_with_result() -> Result<(), String> {let mut account = BankAccount::new(100);account.withdraw(50)?;Ok(())}
}
测试组织和模块化
良好的测试组织可以提高测试的可维护性:
pub struct Calculator;impl Calculator {pub fn add(&self, a: f64, b: f64) -> f64 {a + b}pub fn subtract(&self, a: f64, b: f64) -> f64 {a - b}pub fn multiply(&self, a: f64, b: f64) -> f64 {a * b}pub fn divide(&self, a: f64, b: f64) -> Result<f64, String> {if b == 0.0 {return Err("Division by zero".to_string());}Ok(a / b)}
}#[cfg(test)]
mod calculator_tests {use super::*;// 测试夹具 - 在多个测试间共享的设置fn setup_calculator() -> Calculator {Calculator}// 子模块组织相关测试mod arithmetic_operations {use super::*;#[test]fn test_addition() {let calc = setup_calculator();assert_eq!(calc.add(2.0, 3.0), 5.0);}#[test]fn test_subtraction() {let calc = setup_calculator();assert_eq!(calc.subtract(5.0, 3.0), 2.0);}#[test]fn test_multiplication() {let calc = setup_calculator();assert_eq!(calc.multiply(2.0, 3.0), 6.0);}}mod division_tests {use super::*;#[test]fn test_division() {let calc = setup_calculator();assert_eq!(calc.divide(6.0, 2.0).unwrap(), 3.0);}#[test]fn test_division_by_zero() {let calc = setup_calculator();assert!(calc.divide(6.0, 0.0).is_err());}}// 参数化测试模式mod parameterized_tests {use super::*;// 使用宏创建参数化测试macro_rules! arithmetic_test {($name:ident, $operation:ident, $a:expr, $b:expr, $expected:expr) => {#[test]fn $name() {let calc = setup_calculator();assert_eq!(calc.$operation($a, $b), $expected);}};}arithmetic_test!(add_positive, add, 2.0, 3.0, 5.0);arithmetic_test!(add_negative, add, -2.0, -3.0, -5.0);arithmetic_test!(subtract_positive, subtract, 5.0, 3.0, 2.0);arithmetic_test!(multiply_positive, multiply, 2.0, 3.0, 6.0);}
}
12.2 组织集成测试
集成测试基础
集成测试用于测试库的公共API,验证多个模块如何协同工作。在Rust中,集成测试放在项目根目录的tests文件夹中。
项目结构
my_project/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ └── main.rs
└── tests/├── common/│ └── mod.rs├── integration_test1.rs├── integration_test2.rs└── integration_test3.rs
基本的集成测试
src/lib.rs:
pub struct User {pub id: u32,pub name: String,pub email: String,
}impl User {pub fn new(id: u32, name: &str, email: &str) -> Self {User {id,name: name.to_string(),email: email.to_string(),}}pub fn is_valid(&self) -> bool {!self.name.is_empty() && self.email.contains('@')}
}pub struct UserRepository {users: Vec<User>,
}impl UserRepository {pub fn new() -> Self {UserRepository { users: Vec::new() }}pub fn add_user(&mut self, user: User) -> Result<(), String> {if !user.is_valid() {return Err("Invalid user".to_string());}if self.users.iter().any(|u| u.id == user.id) {return Err("User ID already exists".to_string());}self.users.push(user);Ok(())}pub fn find_by_id(&self, id: u32) -> Option<&User> {self.users.iter().find(|u| u.id == id)}pub fn find_by_name(&self, name: &str) -> Vec<&User> {self.users.iter().filter(|u| u.name.to_lowercase().contains(&name.to_lowercase())).collect()}pub fn count(&self) -> usize {self.users.len()}
}
tests/integration_test1.rs:
use my_project::{User, UserRepository};#[test]
fn test_user_creation() {let user = User::new(1, "Alice", "alice@example.com");assert!(user.is_valid());assert_eq!(user.name, "Alice");assert_eq!(user.email, "alice@example.com");
}#[test]
fn test_user_repository_basic_operations() {let mut repo = UserRepository::new();// 测试添加用户let user = User::new(1, "Alice", "alice@example.com");assert!(repo.add_user(user).is_ok());assert_eq!(repo.count(), 1);// 测试查找用户let found_user = repo.find_by_id(1);assert!(found_user.is_some());assert_eq!(found_user.unwrap().name, "Alice");// 测试重复IDlet duplicate_user = User::new(1, "Bob", "bob@example.com");assert!(repo.add_user(duplicate_user).is_err());// 测试无效用户let invalid_user = User::new(2, "", "invalid-email");assert!(repo.add_user(invalid_user).is_err());
}#[test]
fn test_user_search() {let mut repo = UserRepository::new();repo.add_user(User::new(1, "Alice Smith", "alice@example.com")).unwrap();repo.add_user(User::new(2, "Bob Johnson", "bob@example.com")).unwrap();repo.add_user(User::new(3, "Charlie Brown", "charlie@example.com")).unwrap();// 测试按名称搜索let results = repo.find_by_name("alic");assert_eq!(results.len(), 1);assert_eq!(results[0].name, "Alice Smith");// 测试不区分大小写let results = repo.find_by_name("ALICE");assert_eq!(results.len(), 1);// 测试部分匹配let results = repo.find_by_name("o");assert_eq!(results.len(), 2); // Bob Johnson, Charlie Brown
}
共享测试代码
在tests/common/mod.rs中定义共享的测试工具:
tests/common/mod.rs:
// 共享的测试工具和辅助函数pub fn setup_test_users() -> Vec<crate::User> {vec![crate::User::new(1, "Alice", "alice@example.com"),crate::User::new(2, "Bob", "bob@example.com"),crate::User::new(3, "Charlie", "charlie@example.com"),]
}pub fn create_test_repository() -> crate::UserRepository {let mut repo = crate::UserRepository::new();let users = setup_test_users();for user in users {repo.add_user(user).unwrap();}repo
}// 测试构建器模式
pub struct UserBuilder {id: u32,name: String,email: String,
}impl UserBuilder {pub fn new() -> Self {UserBuilder {id: 1,name: "Test User".to_string(),email: "test@example.com".to_string(),}}pub fn with_id(mut self, id: u32) -> Self {self.id = id;self}pub fn with_name(mut self, name: &str) -> Self {self.name = name.to_string();self}pub fn with_email(mut self, email: &str) -> Self {self.email = email.to_string();self}pub fn build(self) -> crate::User {crate::User::new(self.id, &self.name, &self.email)}
}
tests/integration_test2.rs:
// 在集成测试中使用共享模块
mod common;
use my_project::UserRepository;#[test]
fn test_with_shared_setup() {let repo = common::create_test_repository();assert_eq!(repo.count(), 3);let user = repo.find_by_id(1);assert!(user.is_some());assert_eq!(user.unwrap().name, "Alice");
}#[test]
fn test_user_builder() {let user = common::UserBuilder::new().with_id(100).with_name("Custom User").with_email("custom@example.com").build();assert!(user.is_valid());assert_eq!(user.id, 100);assert_eq!(user.name, "Custom User");assert_eq!(user.email, "custom@example.com");
}
复杂的集成测试场景
tests/complex_scenarios.rs:
use my_project::{User, UserRepository};
mod common;#[test]
fn test_concurrent_operations() {let mut repo = UserRepository::new();// 模拟并发操作let users = vec![User::new(1, "User1", "user1@example.com"),User::new(2, "User2", "user2@example.com"),User::new(3, "User3", "user3@example.com"),];// 批量添加用户for user in users {repo.add_user(user).expect("Failed to add user");}assert_eq!(repo.count(), 3);// 测试复杂的查询场景let found_users = repo.find_by_name("user");assert_eq!(found_users.len(), 3);// 测试边界情况let non_existent = repo.find_by_id(999);assert!(non_existent.is_none());
}#[test]
fn test_error_scenarios() {let mut repo = UserRepository::new();// 测试无效数据let invalid_users = vec![User::new(0, "", "invalid"), // 空名字,无效邮箱User::new(1, "Valid", "invalid-email"), // 无效邮箱User::new(2, "", "valid@example.com"), // 空名字];for user in invalid_users {assert!(repo.add_user(user).is_err(), "Should reject invalid user");}// 验证没有用户被添加assert_eq!(repo.count(), 0);
}// 测试性能特征
#[test]
fn test_performance_characteristics() {let mut repo = UserRepository::new();// 添加大量用户测试性能for i in 0..1000 {let user = User::new(i, &format!("User{}", i), &format!("user{}@example.com", i));repo.add_user(user).unwrap();}assert_eq!(repo.count(), 1000);// 测试查找性能let start = std::time::Instant::now();let _ = repo.find_by_name("User500");let duration = start.elapsed();// 验证查找操作在合理时间内完成assert!(duration < std::time::Duration::from_millis(100), "Lookup took too long: {:?}", duration);
}
12.3 测试驱动开发实践
TDD循环:红-绿-重构
测试驱动开发(TDD)是一种软件开发方法,强调在编写实现代码之前先编写测试。
TDD基础示例:栈实现
第一步:编写失败的测试(红)
#[cfg(test)]
mod tdd_stack_tests {#[test]fn test_new_stack_is_empty() {let stack = Stack::new();assert!(stack.is_empty());assert_eq!(stack.size(), 0);}#[test]fn test_push_increases_size() {let mut stack = Stack::new();stack.push(42);assert!(!stack.is_empty());assert_eq!(stack.size(), 1);}#[test]fn test_pop_returns_pushed_value() {let mut stack = Stack::new();stack.push(42);let value = stack.pop();assert_eq!(value, Some(42));}#[test]fn test_pop_empty_stack_returns_none() {let mut stack = Stack::new();assert_eq!(stack.pop(), None);}#[test]fn test_last_in_first_out() {let mut stack = Stack::new();stack.push(1);stack.push(2);stack.push(3);assert_eq!(stack.pop(), Some(3));assert_eq!(stack.pop(), Some(2));assert_eq!(stack.pop(), Some(1));assert_eq!(stack.pop(), None);}
}
第二步:实现最小代码使测试通过(绿)
pub struct Stack<T> {elements: Vec<T>,
}impl<T> Stack<T> {pub fn new() -> Self {Stack { elements: Vec::new() }}pub fn push(&mut self, value: T) {self.elements.push(value);}pub fn pop(&mut self) -> Option<T> {self.elements.pop()}pub fn is_empty(&self) -> bool {self.elements.is_empty()}pub fn size(&self) -> usize {self.elements.len()}
}
第三步:重构改进代码
impl<T> Default for Stack<T> {fn default() -> Self {Self::new()}
}impl<T> Stack<T> {pub fn peek(&self) -> Option<&T> {self.elements.last()}pub fn clear(&mut self) {self.elements.clear();}
}
实战TDD:购物车系统
让我们通过TDD实现一个完整的购物车系统:
第一阶段:基本购物车功能
测试1:创建空购物车
#[cfg(test)]
mod shopping_cart_tests {use super::*;#[test]fn test_new_cart_is_empty() {let cart = ShoppingCart::new();assert!(cart.is_empty());assert_eq!(cart.item_count(), 0);assert_eq!(cart.total_price(), 0.0);}
}
实现1:
pub struct ShoppingCart {items: Vec<CartItem>,
}impl ShoppingCart {pub fn new() -> Self {ShoppingCart { items: Vec::new() }}pub fn is_empty(&self) -> bool {self.items.is_empty()}pub fn item_count(&self) -> usize {self.items.len()}pub fn total_price(&self) -> f64 {0.0 // 初始实现}
}struct CartItem; // 占位符
测试2:添加商品
#[test]
fn test_add_item_to_cart() {let mut cart = ShoppingCart::new();let item = Product::new("Rust Book", 39.99);cart.add_item(item, 1);assert!(!cart.is_empty());assert_eq!(cart.item_count(), 1);
}
实现2:
pub struct Product {name: String,price: f64,
}impl Product {pub fn new(name: &str, price: f64) -> Self {Product {name: name.to_string(),price,}}
}pub struct CartItem {product: Product,quantity: u32,
}impl ShoppingCart {pub fn add_item(&mut self, product: Product, quantity: u32) {let item = CartItem { product, quantity };self.items.push(item);}
}
第二阶段:价格计算
测试3:计算总价
#[test]
fn test_calculate_total_price() {let mut cart = ShoppingCart::new();cart.add_item(Product::new("Rust Book", 39.99), 1);cart.add_item(Product::new("Rust T-Shirt", 25.50), 2);// 39.99 + (25.50 * 2) = 90.99assert_eq!(cart.total_price(), 90.99);
}
实现3:
impl ShoppingCart {pub fn total_price(&self) -> f64 {self.items.iter().map(|item| item.product.price * item.quantity as f64).sum()}
}
第三阶段:高级功能
测试4:移除商品
#[test]
fn test_remove_item() {let mut cart = ShoppingCart::new();let product = Product::new("Rust Book", 39.99);cart.add_item(product.clone(), 1);assert_eq!(cart.item_count(), 1);cart.remove_item(&product.name);assert!(cart.is_empty());
}#[test]
fn test_remove_nonexistent_item() {let mut cart = ShoppingCart::new();cart.remove_item("Nonexistent");// 不应该panic或出错
}
实现4:
impl ShoppingCart {pub fn remove_item(&mut self, product_name: &str) {self.items.retain(|item| item.product.name != product_name);}
}
测试5:更新商品数量
#[test]
fn test_update_quantity() {let mut cart = ShoppingCart::new();let product = Product::new("Rust Book", 39.99);cart.add_item(product.clone(), 1);assert_eq!(cart.get_quantity(&product.name), Some(1));cart.update_quantity(&product.name, 3);assert_eq!(cart.get_quantity(&product.name), Some(3));assert_eq!(cart.total_price(), 39.99 * 3.0);
}#[test]
fn test_update_nonexistent_product() {let mut cart = ShoppingCart::new();cart.update_quantity("Nonexistent", 5);// 不应该panic
}
实现5:
impl ShoppingCart {pub fn get_quantity(&self, product_name: &str) -> Option<u32> {self.items.iter().find(|item| item.product.name == product_name).map(|item| item.quantity)}pub fn update_quantity(&mut self, product_name: &str, new_quantity: u32) {if let Some(item) = self.items.iter_mut().find(|item| item.product.name == product_name) {item.quantity = new_quantity;}}
}
TDD最佳实践
1. 小步前进
// 不好的做法:一次测试太多功能
#[test]
fn test_complete_shopping_flow() {// 太复杂,难以调试
}// 好的做法:分解为小测试
#[test]
fn test_add_single_item() { /* ... */ }
#[test]
fn test_add_multiple_items() { /* ... */ }
#[test]
fn test_remove_item() { /* ... */ }
#[test]
fn test_calculate_total() { /* ... */ }
2. 测试边界条件
#[cfg(test)]
mod boundary_tests {use super::*;#[test]fn test_zero_quantity() {let mut cart = ShoppingCart::new();cart.add_item(Product::new("Test", 10.0), 0);assert!(cart.is_empty());}#[test]fn test_negative_price() {// 应该拒绝负价格let product = Product::new("Invalid", -10.0);// 验证价格验证逻辑}#[test]fn test_very_large_quantities() {let mut cart = ShoppingCart::new();cart.add_item(Product::new("Bulk Item", 0.01), u32::MAX);// 测试整数溢出等情况}
}
3. 测试驱动设计
// 测试驱动我们设计更好的API
#[test]
fn test_cart_serialization() {let mut cart = ShoppingCart::new();cart.add_item(Product::new("Item", 10.0), 2);let json = cart.to_json();let deserialized_cart = ShoppingCart::from_json(&json);assert_eq!(cart.total_price(), deserialized_cart.total_price());
}// 这个测试驱动我们实现序列化功能
impl ShoppingCart {pub fn to_json(&self) -> String {// 实现序列化String::new()}pub fn from_json(_json: &str) -> Self {// 实现反序列化ShoppingCart::new()}
}
12.4 性能测试与基准测试
内置基准测试
Rust支持内置的基准测试,但需要Nightly版本:
#![feature(test)]
extern crate test;use test::Bencher;pub fn fibonacci_recursive(n: u32) -> u32 {if n <= 1 {n} else {fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)}
}pub fn fibonacci_iterative(n: u32) -> u32 {if n <= 1 {return n;}let mut a = 0;let mut b = 1;for _ in 2..=n {let temp = a + b;a = b;b = temp;}b
}#[cfg(test)]
mod benchmarks {use super::*;use test::Bencher;#[bench]fn bench_fibonacci_recursive(b: &mut Bencher) {b.iter(|| {fibonacci_recursive(20)});}#[bench]fn bench_fibonacci_iterative(b: &mut Bencher) {b.iter(|| {fibonacci_iterative(20)});}#[bench]fn bench_large_iterations(b: &mut Bencher) {b.iter(|| {// 测试大量迭代的性能(0..1000).map(fibonacci_iterative).sum::<u32>()});}
}
使用Criterion.rs进行基准测试
Criterion.rs是稳定的Rust中最流行的基准测试库:
Cargo.toml配置:
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }[[bench]]
name = "my_benchmark"
harness = false
benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use my_project::{fibonacci_iterative, fibonacci_recursive, ShoppingCart, Product};// 斐波那契数列基准测试
fn fibonacci_benchmarks(c: &mut Criterion) {let mut group = c.benchmark_group("Fibonacci");group.bench_function("recursive_20", |b| {b.iter(|| fibonacci_recursive(black_box(20)))});group.bench_function("iterative_20", |b| {b.iter(|| fibonacci_iterative(black_box(20)))});group.bench_function("recursive_30", |b| {b.iter(|| fibonacci_recursive(black_box(30)))});group.bench_function("iterative_30", |b| {b.iter(|| fibonacci_iterative(black_box(30)))});group.finish();
}// 购物车性能基准测试
fn shopping_cart_benchmarks(c: &mut Criterion) {c.bench_function("add_1000_items", |b| {b.iter(|| {let mut cart = ShoppingCart::new();for i in 0..1000 {cart.add_item(Product::new(&format!("Product {}", i), i as f64),1);}black_box(cart);});});c.bench_function("calculate_total_1000_items", |b| {let mut cart = ShoppingCart::new();for i in 0..1000 {cart.add_item(Product::new(&format!("Product {}", i), i as f64),1);}b.iter(|| {black_box(cart.total_price());});});c.bench_function("search_cart", |b| {let mut cart = ShoppingCart::new();for i in 0..1000 {cart.add_item(Product::new(&format!("Product {}", i), i as f64),1);}b.iter(|| {black_box(cart.get_quantity("Product 500"));});});
}// 比较基准测试
fn comparison_benchmarks(c: &mut Criterion) {let mut group = c.benchmark_group("Comparisons");group.bench_function("vector_push", |b| {b.iter(|| {let mut vec = Vec::new();for i in 0..1000 {vec.push(black_box(i));}black_box(vec);});});group.bench_function("vector_with_capacity", |b| {b.iter(|| {let mut vec = Vec::with_capacity(1000);for i in 0..1000 {vec.push(black_box(i));}black_box(vec);});});group.finish();
}criterion_group!(benches,fibonacci_benchmarks,shopping_cart_benchmarks,comparison_benchmarks
);
criterion_main!(benches);
性能回归测试
// 性能回归测试确保优化不会引入性能退化
#[cfg(test)]
mod performance_regression {use super::*;use std::time::{Duration, Instant};#[test]fn test_cart_performance_regression() {let mut cart = ShoppingCart::new();// 添加大量商品for i in 0..5000 {cart.add_item(Product::new(&format!("Item {}", i), i as f64), 1);}// 测试总价计算性能let start = Instant::now();let total = cart.total_price();let duration = start.elapsed();// 性能断言:总价计算应该在1毫秒内完成assert!(duration < Duration::from_millis(1), "Performance regression: total_price took {:?}", duration);// 验证结果正确性let expected_total: f64 = (0..5000).sum::<i32>() as f64;assert!((total - expected_total).abs() < f64::EPSILON);}#[test]fn test_search_performance() {let mut cart = ShoppingCart::new();// 准备测试数据for i in 0..10000 {cart.add_item(Product::new(&format!("Product{}", i), 10.0), 1);}// 测试搜索性能let start = Instant::now();for i in 0..100 {black_box(cart.get_quantity(&format!("Product{}", i * 100)));}let duration = start.elapsed();// 100次搜索应该在10毫秒内完成assert!(duration < Duration::from_millis(10),"Search performance regression: {:?}", duration);}
}
内存使用测试
#[cfg(test)]
mod memory_tests {use super::*;use std::mem;#[test]fn test_memory_usage() {// 测试空购物车内存使用let empty_cart = ShoppingCart::new();let empty_size = mem::size_of_val(&empty_cart);// 测试包含商品的购物车内存使用let mut full_cart = ShoppingCart::new();for i in 0..100 {full_cart.add_item(Product::new(&format!("Item {}", i), 10.0), 1);}let full_size = mem::size_of_val(&full_cart);// 验证内存使用合理let item_size = mem::size_of::<CartItem>();let expected_growth = 100 * item_size;let actual_growth = full_size - empty_size;// 允许一些额外的开销(指针、容量等)assert!(actual_growth <= expected_growth * 2, "Excessive memory usage: expected ~{}, got {}", expected_growth, actual_growth);}#[test]fn test_no_memory_leaks() {// 这个测试验证没有内存泄漏// 可以通过Valgrind或Rust的MIRI来运行let initial_memory = get_memory_usage();{let mut cart = ShoppingCart::new();for i in 0..1000 {cart.add_item(Product::new(&format!("Temp {}", i), 10.0), 1);}// cart在这里离开作用域,应该释放所有内存}let final_memory = get_memory_usage();// 内存使用应该回到初始水平(允许一些缓存)assert!((final_memory - initial_memory) < 1024 * 1024, // 1MB"Possible memory leak: initial={}, final={}", initial_memory, final_memory);}fn get_memory_usage() -> usize {// 在实际测试中,可以使用外部工具或特定平台的API// 这里返回一个占位符值0}
}
并发性能测试
#[cfg(test)]
mod concurrency_tests {use super::*;use std::sync::Arc;use std::thread;#[test]fn test_concurrent_access() {let cart = Arc::new(std::sync::Mutex::new(ShoppingCart::new()));let mut handles = vec![];// 创建多个线程同时操作购物车for thread_id in 0..10 {let cart_clone = Arc::clone(&cart);let handle = thread::spawn(move || {for i in 0..100 {let product_name = format!("Thread{}-Product{}", thread_id, i);let mut cart = cart_clone.lock().unwrap();cart.add_item(Product::new(&product_name, 10.0), 1);}});handles.push(handle);}// 等待所有线程完成for handle in handles {handle.join().unwrap();}// 验证最终状态let final_cart = cart.lock().unwrap();assert_eq!(final_cart.item_count(), 10 * 100);}#[test]fn test_read_only_concurrent_access() {let mut cart = ShoppingCart::new();for i in 0..1000 {cart.add_item(Product::new(&format!("Product{}", i), 10.0), 1);}let cart = Arc::new(cart);let mut handles = vec![];// 多个线程同时读取购物车for _ in 0..10 {let cart_clone = Arc::clone(&cart);let handle = thread::spawn(move || {for _ in 0..100 {let total = cart_clone.total_price();assert_eq!(total, 1000.0 * 10.0);}});handles.push(handle);}for handle in handles {handle.join().unwrap();}}
}
测试最佳实践总结
1. 测试组织结构
// 好的测试组织
#[cfg(test)]
mod tests {use super::*;mod unit_tests {// 单元测试在这里}mod integration_tests {// 集成测试在这里}mod property_tests {// 属性测试在这里}
}
2. 测试命名约定
#[cfg(test)]
mod naming_conventions {// 好的测试命名#[test]fn test_function_name_scenario_expected_result() {// test_add_negative_numbers_returns_negative_sum}#[test]fn when_condition_then_expected_behavior() {// when_user_not_found_then_returns_error}// 避免的命名#[test]fn test1() { // 没有描述性// ...}
}
3. 测试隔离
#[cfg(test)]
mod test_isolation {use super::*;// 每个测试应该独立运行#[test]fn test_independent_1() {let mut cart = ShoppingCart::new(); // 创建新的实例// ...}#[test] fn test_independent_2() {let mut cart = ShoppingCart::new(); // 创建新的实例// ...}// 避免共享状态// static SHARED_STATE: Mutex<u32> = Mutex::new(0); // 不要这样做!
}
4. 全面的测试覆盖
#[cfg(test)]
mod comprehensive_coverage {use super::*;// 正常情况测试#[test]fn test_normal_operation() { /* ... */ }// 边界情况测试 #[test]fn test_boundary_conditions() { /* ... */ }// 错误情况测试#[test]fn test_error_conditions() { /* ... */ }// 性能测试#[test]fn test_performance() { /* ... */ }// 安全测试#[test]fn test_security() { /* ... */ }
}
通过本章的深入学习,你应该已经掌握了Rust测试的完整知识体系。从基础的单元测试到复杂的集成测试,从TDD实践到性能基准测试,这些技能将帮助你构建更加可靠、可维护和高性能的Rust应用程序。
在下一章中,我们将探讨Rust的函数式语言特性,包括闭包和迭代器,这些特性使得Rust能够以函数式编程风格编写出既安全又高效的代码。
