Communitygithub.com

Rust 测试模式

涵盖单元测试、集成测试、异步测试、属性测试、模拟和覆盖率检查的 Rust 测试模式。遵循 TDD 方法论。编写 Rust 测试时使用——包括单元测试、集成测试、异步测试、属性测试或覆盖率检查。

Rust 测试模式 是什么?

该技能提供了遵循 TDD(测试驱动开发)方法论的 Rust 语言全面测试模式集合。它详细介绍了如何按照 RED-GREEN-REFACTOR 循环组织测试开发流程,涵盖从单元测试、集成测试到异步测试的各类测试类型。同时包括使用 rstest 进行参数化测试、proptest 进行基于属性的测试、mockall 进行依赖模拟,以及 Criterion 进行基准测试的实践指南。技能还提供了测试覆盖率目标(如关键业务逻辑 100%,公共 API 90%+)和 CI 集成示例,帮助开发者在 Rust 项目中建立可靠、可维护的测试体系。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/rust-testing

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Rust 测试模式 是做什么的?

全面的 Rust 测试模式,用于编写遵循 TDD 方法论的可靠、可维护的测试。

使用时机

  • 编写新的 Rust 函数、方法或 trait 时
  • 为现有代码添加测试覆盖时
  • 为性能关键代码创建基准测试时
  • 为输入验证实现基于属性的测试时
  • 在 Rust 项目中遵循 TDD 工作流时

工作原理

  1. 识别目标代码 — 找到需要测试的函数、trait 或模块
  2. 编写测试 — 在 #[cfg(test)] 模块中使用 #[test],或使用 rstest 进行参数化测试,proptest 进行基于属性的测试
  3. 模拟依赖 — 使用 mockall 隔离测试单元
  4. 运行测试(RED) — 验证测试按预期失败
  5. 实现(GREEN) — 编写最小代码使测试通过
  6. 重构 — 改进代码同时保持测试通过
  7. 检查覆盖率 — 使用 cargo-llvm-cov,目标 80%+

Rust 的 TDD 工作流

RED-GREEN-REFACTOR 循环

RED     → 首先编写一个会失败的测试
GREEN   → 编写最少代码使测试通过
REFACTOR → 改进代码同时保持测试通过
REPEAT  → 继续下一个需求

Rust 中逐步执行 TDD

// RED: 先编写测试,使用 todo!() 作为占位符
pub fn add(a: i32, b: i32) -> i32 { todo!() }

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() { assert_eq!(add(2, 3), 5); }
}
// cargo test → panics at 'not yet implemented'
// GREEN: 将 todo!() 替换为最小实现
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → PASS,然后 REFACTOR 同时保持测试通过

单元测试

模块级测试组织

// src/user.rs
pub struct User {
    pub name: String,
    pub email: String,
}

impl User {
    pub fn new(name: impl Into<String>, email: impl Into<String>) -> Result<Self, String> {
        let email = email.into();
        if !email.contains('@') {
            return Err(format!("invalid email: {email}"));
        }
        Ok(Self { name: name.into(), email })
    }

    pub fn display_name(&self) -> &str {
        &self.name
    }
}

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

    #[test]
    fn creates_user_with_valid_email() {
        let user = User::new("Alice", "[email protected]").unwrap();
        assert_eq!(user.display_name(), "Alice");
        assert_eq!(user.email, "[email protected]");
    }

    #[test]
    fn rejects_invalid_email() {
        let result = User::new("Bob", "not-an-email");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("invalid email"));
    }
}

断言宏

assert_eq!(2 + 2, 4);                                    // 相等
assert_ne!(2 + 2, 5);                                    // 不等
assert!(vec![1, 2, 3].contains(&2));                     // 布尔值
assert_eq!(value, 42, "expected 42 but got {value}");    // 自定义消息
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON);   // 浮点数比较

错误和恐慌测试

测试 Result 返回值

#[test]
fn parse_returns_error_for_invalid_input() {
    let result = parse_config("}{");
    assert!(result.is_err());

    // 断言特定的错误变体
    let err = result.unwrap_err();
    assert!(matches!(err, ConfigError::ParseError(_)));
}

#[test]
fn parse_succeeds_for_valid_input() -> Result<(), Box<dyn std::error::Error>> {
    let config = parse_config(r#"{"port": 8080}"#)?;
    assert_eq!(config.port, 8080);
    Ok(()) // 如果任何 ? 返回 Err,测试失败
}

测试恐慌

#[test]
#[should_panic]
fn panics_on_empty_input() {
    process(&[]);
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn panics_with_specific_message() {
    let v: Vec<i32> = vec![];
    let _ = v[0];
}

集成测试

文件结构

my_crate/
├── src/
│   └── lib.rs
├── tests/              # 集成测试
│   ├── api_test.rs     # 每个文件是一个独立的测试二进制文件
│   ├── db_test.rs
│   └── common/         # 共享测试工具
│       └── mod.rs

编写集成测试

// tests/api_test.rs
use my_crate::{App, Config};

#[test]
fn full_request_lifecycle() {
    let config = Config::test_default();
    let app = App::new(config);

    let response = app.handle_request("/health");
    assert_eq!(response.status, 200);
    assert_eq!(response.body, "OK");
}

异步测试

使用 Tokio

#[tokio::test]
async fn fetches_data_successfully() {
    let client = TestClient::new().await;
    let result = client.get("/data").await;
    assert!(result.is_ok());
    assert_eq!(result.unwrap().items.len(), 3);
}

#[tokio::test]
async fn handles_timeout() {
    use std::time::Duration;
    let result = tokio::time::timeout(
        Duration::from_millis(100),
        slow_operation(),
    ).await;

    assert!(result.is_err(), "should have timed out");
}

测试组织模式

使用 rstest 进行参数化测试

use rstest::{rstest, fixture};

#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_string_length(#[case] input: &str, #[case] expected: usize) {
    assert_eq!(input.len(), expected);
}

// 夹具
#[fixture]
fn test_db() -> TestDb {
    TestDb::new_in_memory()
}

#[rstest]
fn test_insert(test_db: TestDb) {
    test_db.insert("key", "value");
    assert_eq!(test_db.get("key"), Some("value".into()));
}

测试辅助函数

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

    /// 创建一个具有合理默认值的测试用户。
    fn make_user(name: &str) -> User {
        User::new(name, &format!("{name}@test.com")).unwrap()
    }

    #[test]
    fn user_display() {
        let user = make_user("alice");
        assert_eq!(user.display_name(), "alice");
    }
}

使用 proptest 进行基于属性的测试

基本属性测试

use proptest::prelude::*;

proptest! {
    #[test]
    fn encode_decode_roundtrip(input in ".*") {
        let encoded = encode(&input);
        let decoded = decode(&encoded).unwrap();
        assert_eq!(input, decoded);
    }

    #[test]
    fn sort_preserves_length(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        let original_len = vec.len();
        vec.sort();
        assert_eq!(vec.len(), original_len);
    }

    #[test]
    fn sort_produces_ordered_output(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        vec.sort();
        for window in vec.windows(2) {
            assert!(window[0] <= window[1]);
        }
    }
}

自定义策略

use proptest::prelude::*;

fn valid_email() -> impl Strategy<Value = String> {
    ("[a-z]{1,10}", "[a-z]{1,5}")
        .prop_map(|(user, domain)| format!("{user}@{domain}.com"))
}

proptest! {
    #[test]
    fn accepts_valid_emails(email in valid_email()) {
        assert!(User::new("Test", &email).is_ok());
    }
}

使用 mockall 进行模拟

基于 Trait 的模拟

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

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

#[test]
fn service_returns_user_when_found() {
    let mut mock = MockUserRepository::new();
    mock.expect_find_by_id()
        .with(eq(42))
        .times(1)
        .returning(|_| Some(User { id: 42, name: "Alice".into() }));

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

#[test]
fn service_returns_none_when_not_found() {
    let mut mock = MockUserRepository::new();
    mock.expect_find_by_id()
        .returning(|_| None);

    let service = UserService::new(Box::new(mock));
    assert!(service.get_user(99).is_none());
}

文档测试

可执行的文档

/// 将两个数字相加。
///
/// # 示例
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// 解析配置字符串。
///
/// # 错误
///
/// 如果输入不是有效的 TOML,则返回 `Err`。
///
/// ```no_run
/// use my_crate::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use my_crate::parse_config;
///
/// assert!(parse_config("}{").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
    todo!()
}

使用 Criterion 进行基准测试

# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "benchmark"
harness = false
// benches/benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn fibonacci(n: u64) -> u64 {
    match n {
        0 | 1 => n,
        _ => fibonacci(n - 1) + fibonacci(n - 2),
    }
}

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

criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);

测试覆盖率

运行覆盖率

# 安装:cargo install cargo-llvm-cov(或在 CI 中使用 taiki-e/install-action)
cargo llvm-cov                    # 摘要
cargo llvm-cov --html             # HTML 报告
cargo llvm-cov --lcov > lcov.info # 用于 CI 的 LCOV 格式
cargo llvm-cov --fail-under-lines 80  # 低于阈值则失败

覆盖率目标

代码类型目标
关键业务逻辑100%
公共 API90%+
通用代码80%+
生成的 / FFI 绑定排除

测试命令

cargo test                        # 运行所有测试
cargo test -- --nocapture         # 显示 println 输出
cargo test test_name              # 运行匹配模式的测试
cargo test --lib                  # 仅单元测试
cargo test --test api_test        # 仅集成测试
cargo test --doc                  # 仅文档测试
cargo test --no-fail-fast         # 不因第一个失败而停止
cargo test -- --ignored           # 运行被忽略的测试

最佳实践

应做:

  • 先编写测试(TDD)
  • 使用 #[cfg(test)] 模块进行单元测试
  • 测试行为,而不是实现
  • 使用描述性的测试名称来说明场景
  • 优先使用 assert_eq! 而不是 assert!,以获得更好的错误消息
  • 在返回 Result 的测试中使用 ?,以获得更清晰的错误输出
  • 保持测试独立——不共享可变状态

避免:

  • 在可以使用 Result::is_err() 测试时使用 #[should_panic]
  • 模拟所有内容——在可行时优先使用集成测试
  • 忽略不稳定的测试——修复它们或将其隔离
  • 在测试中使用 sleep()——使用通道、屏障或 tokio::time::pause()
  • 跳过错误路径测试

CI 集成

# GitHub Actions
test:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@stable
      with:
        components: clippy, rustfmt

    - name: 检查格式
      run: cargo fmt --check

    - name: Clippy
      run: cargo clippy -- -D warnings

    - name: 运行测试
      run: cargo test

    - uses: taiki-e/install-action@cargo-llvm-cov

    - name: 覆盖率
      run: cargo llvm-cov --fail-under-lines 80

记住:测试就是文档。它们展示了你的代码应该如何被使用。要清晰地编写它们并保持更新。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能