强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Rust 系统编程语言完全教程 / 第19章:测试

第19章:测试

19.1 单元测试

基本测试

// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("除数不能为零".to_string())
    } else {
        Ok(a / b)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
        assert_eq!(add(-1, 1), 0);
    }

    #[test]
    fn test_divide_success() {
        assert_eq!(divide(10.0, 2.0).unwrap(), 5.0);
    }

    #[test]
    fn test_divide_by_zero() {
        assert!(divide(10.0, 0.0).is_err());
    }
}
# 运行测试
cargo test

# 运行特定测试
cargo test test_add

# 显示输出
cargo test -- --show-output

# 并行/串行
cargo test -- --test-threads=1

19.2 Assert 宏

说明示例
assert!(expr)断言为 trueassert!(x > 0)
assert_eq!(a, b)断言相等assert_eq!(1 + 1, 2)
assert_ne!(a, b)断言不等assert_ne!(x, 0)
debug_assert!(expr)仅 debug 模式debug_assert!(bounds_check)
assert_matches!(expr, pat)模式匹配nightly only
#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        let x = 5;
        assert!(x > 0, "x 应该大于 0, 实际为 {}", x);

        let v = vec![1, 2, 3];
        assert_eq!(v.len(), 3);
        assert_ne!(v[0], 0);

        let name = Some("Alice");
        assert!(name.is_some());
    }

    #[test]
    #[should_panic(expected = "除数不能为零")]
    fn test_panic() {
        let result = std::panic::catch_unwind(|| {
            panic!("除数不能为零");
        });
        // 或者直接测试函数是否 panic
    }
}

19.3 测试组织

测试模块结构

// src/lib.rs
pub fn is_even(n: i32) -> bool {
    n % 2 == 0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_even_numbers() {
        assert!(is_even(2));
        assert!(is_even(4));
        assert!(is_even(0));
    }

    #[test]
    fn test_odd_numbers() {
        assert!(!is_even(1));
        assert!(!is_even(3));
    }
}

测试辅助函数

#[cfg(test)]
mod tests {
    // 测试辅助函数(不加 #[test])
    fn setup() -> Vec<i32> {
        vec![1, 2, 3, 4, 5]
    }

    #[test]
    fn test_sum() {
        let v = setup();
        assert_eq!(v.iter().sum::<i32>(), 15);
    }

    #[test]
    fn test_max() {
        let v = setup();
        assert_eq!(v.iter().max(), Some(&5));
    }
}

19.4 集成测试

目录结构

my_project/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/                  # 集成测试目录
    ├── integration_test.rs
    └── api_test.rs

tests/integration_test.rs

// 集成测试是外部代码,需要导入
use my_project;

#[test]
fn test_public_api() {
    let result = my_project::add(2, 3);
    assert_eq!(result, 5);
}

tests/common/mod.rs

// tests/common/mod.rs
// 共享测试辅助代码(不会被当作独立测试文件)
pub fn setup() {
    // 初始化代码
    println!("测试环境初始化");
}
// tests/integration_test.rs
mod common;

#[test]
fn test_with_setup() {
    common::setup();
    // 测试逻辑...
}

19.5 测试属性

忽略测试

#[cfg(test)]
mod tests {
    #[test]
    #[ignore] // 默认跳过
    fn expensive_test() {
        // 耗时测试
    }

    #[test]
    fn normal_test() {
        assert!(true);
    }
}
# 只运行被忽略的测试
cargo test -- --ignored

# 运行所有测试(包括被忽略的)
cargo test -- --include-ignored

条件测试

#[cfg(test)]
mod tests {
    #[test]
    #[cfg(target_os = "linux")]
    fn linux_only_test() {
        // 仅在 Linux 上运行
    }

    #[test]
    #[cfg(feature = "integration")]
    fn integration_test() {
        // 需要启用 integration feature
    }
}

19.6 测试最佳实践

参数化测试

#[cfg(test)]
mod tests {
    #[test]
    fn test_multiple_cases() {
        let cases = vec![
            (1, 1, 2),
            (0, 0, 0),
            (-1, 1, 0),
            (100, 200, 300),
        ];

        for (a, b, expected) in cases {
            assert_eq!(
                super::add(a, b),
                expected,
                "add({}, {}) 应该等于 {}", a, b, expected
            );
        }
    }
}

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

测试 Result

#[cfg(test)]
mod tests {
    #[test]
    fn test_with_result() -> Result<(), String> {
        let value = "42".parse::<i32>().map_err(|e| e.to_string())?;
        assert_eq!(value, 42);
        Ok(())
    }
}

19.7 业务场景示例

完整测试示例

// src/lib.rs
pub struct Calculator {
    history: Vec<String>,
}

impl Calculator {
    pub fn new() -> Self {
        Self { history: Vec::new() }
    }

    pub fn add(&mut self, a: f64, b: f64) -> f64 {
        let result = a + b;
        self.history.push(format!("{} + {} = {}", a, b, result));
        result
    }

    pub fn divide(&mut self, a: f64, b: f64) -> Result<f64, String> {
        if b == 0.0 {
            return Err("除数不能为零".to_string());
        }
        let result = a / b;
        self.history.push(format!("{} / {} = {}", a, b, result));
        Ok(result)
    }

    pub fn history(&self) -> &[String] {
        &self.history
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        let mut calc = Calculator::new();
        assert_eq!(calc.add(2.0, 3.0), 5.0);
    }

    #[test]
    fn test_divide_success() {
        let mut calc = Calculator::new();
        assert_eq!(calc.divide(10.0, 2.0).unwrap(), 5.0);
    }

    #[test]
    fn test_divide_by_zero() {
        let mut calc = Calculator::new();
        assert!(calc.divide(10.0, 0.0).is_err());
    }

    #[test]
    fn test_history() {
        let mut calc = Calculator::new();
        calc.add(1.0, 2.0);
        calc.add(3.0, 4.0);
        assert_eq!(calc.history().len(), 2);
        assert!(calc.history()[0].contains("3"));
    }
}

19.8 本章小结

要点说明
#[test]标记测试函数
assert!断言宏族
#[should_panic]断言函数 panic
tests/ 目录集成测试
#[ignore]跳过测试
cargo test运行所有测试

扩展阅读

  1. Rust Book - 测试 — 官方教程
  2. nextest — 更快的测试运行器