rust-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rust Testing Core Knowledge

Rust 测试核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
rust
for comprehensive documentation.
Full Reference: See advanced.md for HTTP Testing with wiremock, Property-Based Testing with proptest, Benchmarks with criterion, and Test Coverage with cargo-tarpaulin.
深度参考:使用
mcp__documentation__fetch_docs
工具并指定技术为
rust
以获取完整文档。
完整参考:如需了解使用wiremock进行HTTP测试、使用proptest进行基于属性的测试、使用criterion进行基准测试以及使用cargo-tarpaulin进行测试覆盖率统计,请查看advanced.md

When NOT to Use This Skill

不适用本技能的场景

  • JavaScript/TypeScript Projects - Use
    vitest
    or
    jest
  • Java Projects - Use
    junit
    for Java testing
  • Python Projects - Use
    pytest
    for Python
  • Go Projects - Use
    go-testing
    skill
  • E2E Browser Testing - Use Playwright or Selenium
  • JavaScript/TypeScript项目 - 使用
    vitest
    jest
  • Java项目 - 使用
    junit
    进行Java测试
  • Python项目 - 使用
    pytest
    进行Python测试
  • Go项目 - 使用
    go-testing
    技能
  • 端到端浏览器测试 - 使用Playwright或Selenium

Basic Testing

基础测试

Unit Tests

单元测试

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

pub fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(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), Some(5.0));
    }

    #[test]
    fn test_divide_by_zero() {
        assert_eq!(divide(10.0, 0.0), None);
    }
}
rust
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

pub fn divide(a: f64, b: f64) -> Option<f64> {
    if b == 0.0 {
        None
    } else {
        Some(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), Some(5.0));
    }

    #[test]
    fn test_divide_by_zero() {
        assert_eq!(divide(10.0, 0.0), None);
    }
}

Running Tests

运行测试

bash
undefined
bash
undefined

Run all tests

运行所有测试

cargo test
cargo test

Run specific test

运行指定测试

cargo test test_add
cargo test test_add

Run tests in specific module

运行指定模块中的测试

cargo test tests::
cargo test tests::

Run tests with output

运行测试并显示输出

cargo test -- --nocapture
cargo test -- --nocapture

Run tests sequentially

按顺序运行测试

cargo test -- --test-threads=1
cargo test -- --test-threads=1

Run ignored tests

运行被忽略的测试

cargo test -- --ignored
undefined
cargo test -- --ignored
undefined

Assertions

断言

rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        // Equality
        assert_eq!(2 + 2, 4);
        assert_ne!(2 + 2, 5);

        // Boolean
        assert!(true);
        assert!(!false);

        // Custom message
        assert!(1 + 1 == 2, "Math is broken!");
        assert_eq!(2 + 2, 4, "Expected {} but got {}", 4, 2 + 2);
    }

    #[test]
    fn test_floating_point() {
        let result = 0.1 + 0.2;
        let expected = 0.3;

        // Approximate comparison for floats
        assert!((result - expected).abs() < 1e-10);
    }
}
rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_assertions() {
        // 相等断言
        assert_eq!(2 + 2, 4);
        assert_ne!(2 + 2, 5);

        // 布尔断言
        assert!(true);
        assert!(!false);

        // 自定义消息
        assert!(1 + 1 == 2, "数学运算出错了!");
        assert_eq!(2 + 2, 4, "预期值为{},但实际得到{}", 4, 2 + 2);
    }

    #[test]
    fn test_floating_point() {
        let result = 0.1 + 0.2;
        let expected = 0.3;

        // 浮点数近似比较
        assert!((result - expected).abs() < 1e-10);
    }
}

Expected Panics

预期恐慌测试

rust
pub fn divide_or_panic(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Cannot divide by zero!");
    }
    a / b
}

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

    #[test]
    #[should_panic]
    fn test_panic() {
        divide_or_panic(10, 0);
    }

    #[test]
    #[should_panic(expected = "Cannot divide by zero")]
    fn test_panic_message() {
        divide_or_panic(10, 0);
    }
}
rust
pub fn divide_or_panic(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("不能除以零!");
    }
    a / b
}

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

    #[test]
    #[should_panic]
    fn test_panic() {
        divide_or_panic(10, 0);
    }

    #[test]
    #[should_panic(expected = "Cannot divide by zero")]
    fn test_panic_message() {
        divide_or_panic(10, 0);
    }
}

Result-Based Tests

基于Result的测试

rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_with_result() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err("Math failed".to_string())
        }
    }
}
rust
#[cfg(test)]
mod tests {
    #[test]
    fn test_with_result() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err("数学运算失败".to_string())
        }
    }
}

Ignored Tests

被忽略的测试

rust
#[test]
#[ignore]
fn expensive_test() {
    // Long running test
    std::thread::sleep(std::time::Duration::from_secs(60));
}

#[test]
#[ignore = "requires database connection"]
fn test_database() {
    // Test that requires external resources
}
rust
#[test]
#[ignore]
fn expensive_test() {
    // 耗时较长的测试
    std::thread::sleep(std::time::Duration::from_secs(60));
}

#[test]
#[ignore = "requires database connection"]
fn test_database() {
    // 需要外部资源的测试
}

Integration Tests

集成测试

rust
// tests/integration_test.rs
use my_crate::{add, divide};

#[test]
fn test_add_integration() {
    assert_eq!(add(100, 200), 300);
}

// tests/common/mod.rs - Shared test utilities
pub fn setup() {
    // Setup code
}

// tests/another_test.rs
mod common;

#[test]
fn test_with_setup() {
    common::setup();
    // Test code
}
rust
// tests/integration_test.rs
use my_crate::{add, divide};

#[test]
fn test_add_integration() {
    assert_eq!(add(100, 200), 300);
}

// tests/common/mod.rs - 共享测试工具
pub fn setup() {
    // 初始化代码
}

// tests/another_test.rs
mod common;

#[test]
fn test_with_setup() {
    common::setup();
    // 测试代码
}

Async Testing

异步测试

Tokio Test

Tokio 测试

toml
undefined
toml
undefined

Cargo.toml

Cargo.toml

[dev-dependencies] tokio = { version = "1", features = ["full", "test-util"] }

```rust
use tokio::time::{sleep, Duration};

async fn async_add(a: i32, b: i32) -> i32 {
    sleep(Duration::from_millis(10)).await;
    a + b
}

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

    #[tokio::test]
    async fn test_async_add() {
        let result = async_add(2, 3).await;
        assert_eq!(result, 5);
    }

    #[tokio::test]
    async fn test_multiple_async() {
        let (a, b) = tokio::join!(
            async_add(1, 2),
            async_add(3, 4)
        );
        assert_eq!(a, 3);
        assert_eq!(b, 7);
    }
}
[dev-dependencies] tokio = { version = "1", features = ["full", "test-util"] }

```rust
use tokio::time::{sleep, Duration};

async fn async_add(a: i32, b: i32) -> i32 {
    sleep(Duration::from_millis(10)).await;
    a + b
}

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

    #[tokio::test]
    async fn test_async_add() {
        let result = async_add(2, 3).await;
        assert_eq!(result, 5);
    }

    #[tokio::test]
    async fn test_multiple_async() {
        let (a, b) = tokio::join!(
            async_add(1, 2),
            async_add(3, 4)
        );
        assert_eq!(a, 3);
        assert_eq!(b, 7);
    }
}

Testing with Time

时间控制测试

rust
use tokio::time::{self, Duration, Instant};

async fn delayed_operation() -> &'static str {
    time::sleep(Duration::from_secs(10)).await;
    "done"
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::time::{pause, advance};

    #[tokio::test]
    async fn test_with_time_control() {
        pause(); // Pause time

        let start = Instant::now();
        let future = delayed_operation();

        // Advance time instantly
        advance(Duration::from_secs(10)).await;

        let result = future.await;
        assert_eq!(result, "done");

        // Very little real time has passed
        assert!(start.elapsed() < Duration::from_secs(1));
    }
}
rust
use tokio::time::{self, Duration, Instant};

async fn delayed_operation() -> &'static str {
    time::sleep(Duration::from_secs(10)).await;
    "done"
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio::time::{pause, advance};

    #[tokio::test]
    async fn test_with_time_control() {
        pause(); // 暂停时间

        let start = Instant::now();
        let future = delayed_operation();

        // 立即推进时间
        advance(Duration::from_secs(10)).await;

        let result = future.await;
        assert_eq!(result, "done");

        // 实际经过的时间非常短
        assert!(start.elapsed() < Duration::from_secs(1));
    }
}

Mocking with mockall

使用mockall进行模拟测试

toml
undefined
toml
undefined

Cargo.toml

Cargo.toml

[dev-dependencies] mockall = "0.12"
undefined
[dev-dependencies] mockall = "0.12"
undefined

Basic Mocking

基础模拟

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

#[automock]
trait Database {
    fn get(&self, key: &str) -> Option<String>;
    fn set(&mut self, key: &str, value: &str) -> bool;
}

struct Service<D: Database> {
    db: D,
}

impl<D: Database> Service<D> {
    fn new(db: D) -> Self {
        Self { db }
    }

    fn get_value(&self, key: &str) -> String {
        self.db.get(key).unwrap_or_else(|| "default".to_string())
    }
}

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

    #[test]
    fn test_get_value_exists() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("key1"))
            .times(1)
            .returning(|_| Some("value1".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("key1"), "value1");
    }

    #[test]
    fn test_get_value_missing() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("missing"))
            .times(1)
            .returning(|_| None);

        let service = Service::new(mock);
        assert_eq!(service.get_value("missing"), "default");
    }
}
rust
use mockall::{automock, predicate::*};

#[automock]
trait Database {
    fn get(&self, key: &str) -> Option<String>;
    fn set(&mut self, key: &str, value: &str) -> bool;
}

struct Service<D: Database> {
    db: D,
}

impl<D: Database> Service<D> {
    fn new(db: D) -> Self {
        Self { db }
    }

    fn get_value(&self, key: &str) -> String {
        self.db.get(key).unwrap_or_else(|| "default".to_string())
    }
}

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

    #[test]
    fn test_get_value_exists() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("key1"))
            .times(1)
            .returning(|_| Some("value1".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("key1"), "value1");
    }

    #[test]
    fn test_get_value_missing() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .with(eq("missing"))
            .times(1)
            .returning(|_| None);

        let service = Service::new(mock);
        assert_eq!(service.get_value("missing"), "default");
    }
}

Mock Expectations

模拟调用预期

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

    #[test]
    fn test_call_count() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .times(3)  // Exactly 3 times
            .returning(|_| Some("value".to_string()));

        let service = Service::new(mock);
        service.get_value("a");
        service.get_value("b");
        service.get_value("c");
    }

    #[test]
    fn test_call_sequence() {
        let mut seq = Sequence::new();
        let mut mock = MockDatabase::new();

        mock.expect_get()
            .with(eq("first"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("1".to_string()));

        mock.expect_get()
            .with(eq("second"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("2".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("first"), "1");
        assert_eq!(service.get_value("second"), "2");
    }
}
rust
#[cfg(test)]
mod tests {
    use super::*;
    use mockall::Sequence;

    #[test]
    fn test_call_count() {
        let mut mock = MockDatabase::new();
        mock.expect_get()
            .times(3)  // 恰好调用3次
            .returning(|_| Some("value".to_string()));

        let service = Service::new(mock);
        service.get_value("a");
        service.get_value("b");
        service.get_value("c");
    }

    #[test]
    fn test_call_sequence() {
        let mut seq = Sequence::new();
        let mut mock = MockDatabase::new();

        mock.expect_get()
            .with(eq("first"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("1".to_string()));

        mock.expect_get()
            .with(eq("second"))
            .times(1)
            .in_sequence(&mut seq)
            .returning(|_| Some("2".to_string()));

        let service = Service::new(mock);
        assert_eq!(service.get_value("first"), "1");
        assert_eq!(service.get_value("second"), "2");
    }
}

Async Mock

异步模拟

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

#[async_trait]
#[automock]
trait AsyncDatabase {
    async fn get(&self, key: &str) -> Option<String>;
    async fn set(&self, key: &str, value: &str) -> bool;
}

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

    #[tokio::test]
    async fn test_async_mock() {
        let mut mock = MockAsyncDatabase::new();
        mock.expect_get()
            .with(eq("key"))
            .times(1)
            .returning(|_| Some("value".to_string()));

        let result = mock.get("key").await;
        assert_eq!(result, Some("value".to_string()));
    }
}
rust
use mockall::{automock, predicate::*};
use async_trait::async_trait;

#[async_trait]
#[automock]
trait AsyncDatabase {
    async fn get(&self, key: &str) -> Option<String>;
    async fn set(&self, key: &str, value: &str) -> bool;
}

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

    #[tokio::test]
    async fn test_async_mock() {
        let mut mock = MockAsyncDatabase::new();
        mock.expect_get()
            .with(eq("key"))
            .times(1)
            .returning(|_| Some("value".to_string()));

        let result = mock.get("key").await;
        assert_eq!(result, Some("value".to_string()));
    }
}

Checklist

检查清单

  • Unit tests for all public functions
  • Integration tests for module interactions
  • Async tests with tokio-test
  • Mock external dependencies
  • Property-based tests for algorithms
  • Benchmarks for performance-critical code
  • Coverage reporting
  • CI integration
  • 为所有公开函数编写单元测试
  • 为模块交互编写集成测试
  • 使用tokio-test编写异步测试
  • 模拟外部依赖
  • 为算法编写基于属性的测试
  • 为性能关键代码编写基准测试
  • 生成覆盖率报告
  • 集成持续集成(CI)

Anti-Patterns

反模式

Anti-PatternWhy It's BadSolution
Testing private functionsCoupled to implementationTest through public API
Not using #[should_panic]Missing error validationTest expected panics explicitly
Shared mutable state in testsFlaky testsUse test isolation
Not using Result<()> in testsCan't use ? operatorReturn Result<(), Error>
Ignoring async testsWrong runtime behaviorUse #[tokio::test] for async
Not benchmarkingPerformance regressionsUse Criterion for benchmarks
Missing property testsEdge cases missedUse proptest for algorithms
反模式危害解决方案
测试私有函数与实现细节耦合通过公开API进行测试
不使用#[should_panic]缺少错误验证显式测试预期的恐慌场景
测试中使用共享可变状态测试结果不稳定确保测试隔离
测试中不使用Result<()>无法使用?运算符返回Result<(), Error>类型
忽略异步测试运行时行为错误对异步测试使用#[tokio::test]
不进行基准测试可能出现性能回归使用Criterion进行基准测试
缺少基于属性的测试可能遗漏边缘情况使用proptest为算法编写测试

Quick Troubleshooting

快速故障排除

ProblemLikely CauseSolution
"test result: FAILED. 0 passed"Panic in testCheck panic message, add proper assertions
Async test timeoutMissing await or infinite loopEnsure all futures are awaited
Mock expectation failedWrong method callsVerify mockall expectations
"cannot find derive macro"Missing dev-dependencyAdd mockall to [dev-dependencies]
Benchmark not runningWrong harness settingSet harness = false in [[bench]]
Coverage tool crashesIncompatible versionUse cargo-tarpaulin or llvm-cov
问题可能原因解决方案
"test result: FAILED. 0 passed"测试中发生恐慌检查恐慌信息,添加正确的断言
异步测试超时缺少await或存在无限循环确保所有future都被await
模拟预期失败方法调用不正确验证mockall的预期设置
"cannot find derive macro"缺少开发依赖在[dev-dependencies]中添加mockall
基准测试无法运行harness设置错误在[[bench]]中设置harness = false
覆盖率工具崩溃版本不兼容使用cargo-tarpaulin或llvm-cov

Reference Documentation

参考文档

  • Unit Tests
  • Async Testing
  • Mocking
  • Advanced Patterns
  • 单元测试
  • 异步测试
  • 模拟测试
  • 高级模式