
文章目录
- [第12章 测试编写](#第12章 测试编写)
-
- [12.1 测试函数编写](#12.1 测试函数编写)
- [12.2 组织集成测试](#12.2 组织集成测试)
- [12.3 测试驱动开发实践](#12.3 测试驱动开发实践)
-
- TDD循环:红-绿-重构
- 实战TDD:购物车系统
- TDD最佳实践
-
- [1. 小步前进](#1. 小步前进)
- [2. 测试边界条件](#2. 测试边界条件)
- [3. 测试驱动设计](#3. 测试驱动设计)
- [12.4 性能测试与基准测试](#12.4 性能测试与基准测试)
- 测试最佳实践总结
-
- [1. 测试组织结构](#1. 测试组织结构)
- [2. 测试命名约定](#2. 测试命名约定)
- [3. 测试隔离](#3. 测试隔离)
- [4. 全面的测试覆盖](#4. 全面的测试覆盖)
第12章 测试编写
测试是软件开发中不可或缺的重要环节,它确保代码的正确性、可靠性和可维护性。Rust语言内置了一个强大而灵活的测试框架,使得编写和运行测试变得异常简单。本章将全面深入地探讨Rust测试的各个方面,从基础的测试函数编写到复杂的集成测试组织,从测试驱动开发实践到性能测试与基准测试。
12.1 测试函数编写
Rust测试框架基础
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运行测试非常简单:
bash
# 运行所有测试
cargo test
# 运行特定测试
cargo test test_add
# 运行测试并显示输出(即使测试通过)
cargo test -- --nocapture
# 多线程运行测试
cargo test -- --test-threads=1
断言宏详解
Rust提供了一系列强大的断言宏,用于验证测试条件:
rust
#[cfg(test)]
mod assertion_tests {
#[test]
fn test_basic_assertions() {
let value = 42;
// assert! - 检查条件是否为true
assert!(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
测试不仅需要验证正常情况,还需要验证错误处理:
rust
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)
}
// 这个方法在特定条件下会panic
pub 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(())
}
}
测试组织和模块化
良好的测试组织可以提高测试的可维护性:
rust
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:
rust
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:
rust
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");
// 测试重复ID
let 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:
rust
// 共享的测试工具和辅助函数
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:
rust
// 在集成测试中使用共享模块
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:
rust
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基础示例:栈实现
第一步:编写失败的测试(红)
rust
#[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);
}
}
第二步:实现最小代码使测试通过(绿)
rust
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()
}
}
第三步:重构改进代码
rust
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:创建空购物车
rust
#[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:
rust
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:添加商品
rust
#[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:
rust
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:计算总价
rust
#[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.99
assert_eq!(cart.total_price(), 90.99);
}
实现3:
rust
impl ShoppingCart {
pub fn total_price(&self) -> f64 {
self.items.iter()
.map(|item| item.product.price * item.quantity as f64)
.sum()
}
}
第三阶段:高级功能
测试4:移除商品
rust
#[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:
rust
impl ShoppingCart {
pub fn remove_item(&mut self, product_name: &str) {
self.items.retain(|item| item.product.name != product_name);
}
}
测试5:更新商品数量
rust
#[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:
rust
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. 小步前进
rust
// 不好的做法:一次测试太多功能
#[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. 测试边界条件
rust
#[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. 测试驱动设计
rust
// 测试驱动我们设计更好的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版本:
rust
#![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配置:
toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]
name = "my_benchmark"
harness = false
benches/my_benchmark.rs:
rust
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);
性能回归测试
rust
// 性能回归测试确保优化不会引入性能退化
#[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);
}
}
内存使用测试
rust
#[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
}
}
并发性能测试
rust
#[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. 测试组织结构
rust
// 好的测试组织
#[cfg(test)]
mod tests {
use super::*;
mod unit_tests {
// 单元测试在这里
}
mod integration_tests {
// 集成测试在这里
}
mod property_tests {
// 属性测试在这里
}
}
2. 测试命名约定
rust
#[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. 测试隔离
rust
#[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. 全面的测试覆盖
rust
#[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能够以函数式编程风格编写出既安全又高效的代码。