Chuyển đến nội dung chính

第 19 課:測試和基準測試

單元測試、整合測試、文件測試。 rstest(夾具、參數化)、mockall。使用 reqwest 進行 API 測試。 Criterion.rs 基準測試。基於屬性的測試,模糊測試。代碼覆蓋率。

💻 程式設計 — 第 19 課 第 19 課:測試和基準測試

Rust:從基礎到高級

第 6 部分:測試、CI/CD 和生產

亞洲開發網

1. 單元測試

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("Division by zero".into()) } else { Ok(a / b) }
}

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

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

    #[test]
    fn test_divide() {
        assert_eq!(divide(10.0, 2.0), Ok(5.0));
    }

    #[test]
    fn test_divide_by_zero() {
        assert!(divide(10.0, 0.0).is_err());
    }

    #[test]
    #[should_panic(expected = "overflow")]
    fn test_overflow() {
        let _ = i8::MAX.checked_add(1).expect("overflow");
    }
}

2. rstest — 夾具和參數化

use rstest::*;

#[fixture]
fn db_pool() -> PgPool {
    // Setup test database
    PgPoolOptions::new().connect_lazy("postgres://test:test@localhost/test").unwrap()
}

#[rstest]
#[case(2, 3, 5)]
#[case(0, 0, 0)]
#[case(-1, 1, 0)]
fn test_add_parametrized(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
    assert_eq!(add(a, b), expected);
}

// Async test
#[rstest]
#[tokio::test]
async fn test_create_product(db_pool: PgPool) {
    let repo = ProductRepository::new(db_pool);
    let product = repo.create("Test", 9.99, None).await.unwrap();
    assert_eq!(product.name, "Test");
}

3. 使用mockall進行模擬

use mockall::{automock, predicate::*};

#[automock]
trait UserRepository {
    async fn find_by_id(&self, id: &str) -> Option<User>;
    async fn save(&self, user: &User) -> Result<(), String>;
}

#[tokio::test]
async fn test_user_service() {
    let mut mock = MockUserRepository::new();

    mock.expect_find_by_id()
        .with(eq("user-1"))
        .times(1)
        .returning(|_| Some(User { id: "user-1".into(), name: "Alice".into() }));

    let service = UserService::new(mock);
    let user = service.get_user("user-1").await.unwrap();
    assert_eq!(user.name, "Alice");
}

4. 標準基準測試

// benches/my_bench.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn fibonacci(n: u64) -> u64 {
    match n {
        0 | 1 => n,
        _ => { let (mut a, mut b) = (0, 1); for _ in 2..=n { let t = a + b; a = b; b = t; } b }
    }
}

fn bench_fibonacci(c: &mut Criterion) {
    c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));

    let mut group = c.benchmark_group("fibonacci");
    for n in [10, 20, 30, 40] {
        group.bench_with_input(format!("fib {}", n), &n, |b, &n| {
            b.iter(|| fibonacci(black_box(n)));
        });
    }
    group.finish();
}

criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
cargo bench
cargo test
cargo test -- --test-threads=1  # Sequential
cargo tarpaulin --out Html       # Coverage

下一篇: Rust 的 Docker 和 CI/CD。