第12章 测试编写

文章目录

第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能够以函数式编程风格编写出既安全又高效的代码。

相关推荐
无敌最俊朗@1 小时前
C++ 并发与同步速查笔记(整理版)
开发语言·c++·算法
熠熠仔1 小时前
QGIS 3.34+ 网络分析基础数据自动化生成:从脚本到应用
python·数据分析
C2H5OH6662 小时前
Netty详解-02
java·websocket·网络协议·tcp/ip·tomcat·netty·nio
测试19982 小时前
Appium使用指南与自动化测试案例详解
自动化测试·软件测试·python·测试工具·职场和发展·appium·测试用例
Elastic 中国社区官方博客2 小时前
Observability:适用于 PHP 的 OpenTelemetry:EDOT PHP 加入 OpenTelemetry 项目
大数据·开发语言·人工智能·elasticsearch·搜索引擎·全文检索·php
csbysj20202 小时前
PHP 魔术常量
开发语言
神仙别闹2 小时前
基于 C++和 Python 实现计算机视觉
c++·python·计算机视觉
狮子不白2 小时前
C#WEB 防重复提交控制
开发语言·前端·程序人生·c#