Files
everything-claude-code/docs/zh-CN/commands/rust-test.md
2026-03-22 15:39:24 -07:00

312 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: 为Rust强制执行TDD工作流。先写测试然后实现。使用cargo-llvm-cov验证80%以上的覆盖率。
---
# Rust TDD 命令
该命令使用 `#[test]`、rstest、proptest 和 mockall 来强制执行 Rust 代码的测试驱动开发方法。
## 该命令的作用
1. **定义类型/特征**:使用 `todo!()` 搭建函数签名
2. **编写测试**创建全面的测试模块RED
3. **运行测试**:验证测试因正确的原因而失败
4. **实现代码**编写通过测试所需的最少代码GREEN
5. **重构**:在保持测试通过的前提下进行改进
6. **检查覆盖率**:使用 cargo-llvm-cov 确保 80%+ 的覆盖率
## 何时使用
在以下情况使用 `/rust-test`
* 实现新的 Rust 函数、方法或特征时
* 为现有 Rust 代码添加测试覆盖时
* 修复错误时(首先编写失败的测试)
* 构建关键业务逻辑时
* 学习 Rust 中的 TDD 工作流程时
## TDD 循环
```
RED -> 先编写失败的测试
GREEN -> 实现最小化代码以通过测试
REFACTOR -> 改进代码,保持测试通过
REPEAT -> 下一个测试用例
```
## 示例会话
````text
User: /rust-test I need a function to validate user registration
Agent:
# TDD Session: Registration Validator
## Step 1: Define Interface
```rust
// src/validator.rs
pub struct RegistrationRequest {
pub name: String,
pub email: String,
pub password: String,
}
pub enum ValidationResult {
Valid,
Invalid(Vec<String>),
}
pub fn validate_registration(request: &RegistrationRequest) -> ValidationResult {
todo!()
}
```
## Step 2: Write Tests (RED)
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_registration_returns_valid() {
let request = RegistrationRequest {
name: "Alice".into(),
email: "alice@example.com".into(),
password: "SecureP@ss1".into(),
};
assert!(matches!(validate_registration(&request), ValidationResult::Valid));
}
#[test]
fn blank_name_returns_invalid() {
let request = RegistrationRequest {
name: "".into(),
email: "alice@example.com".into(),
password: "SecureP@ss1".into(),
};
match validate_registration(&request) {
ValidationResult::Invalid(errors) => {
assert!(errors.contains(&"Name is required".to_string()));
}
ValidationResult::Valid => panic!("expected Invalid"),
}
}
#[test]
fn invalid_email_returns_invalid() {
let request = RegistrationRequest {
name: "Alice".into(),
email: "not-an-email".into(),
password: "SecureP@ss1".into(),
};
assert!(matches!(
validate_registration(&request),
ValidationResult::Invalid(_)
));
}
#[test]
fn short_password_returns_invalid() {
let request = RegistrationRequest {
name: "Alice".into(),
email: "alice@example.com".into(),
password: "short".into(),
};
assert!(matches!(
validate_registration(&request),
ValidationResult::Invalid(_)
));
}
}
```
## Step 3: Run Tests — Verify FAIL
```bash
$ cargo test
running 4 tests
test tests::valid_registration_returns_valid ... FAILED (not yet implemented)
test tests::blank_name_returns_invalid ... FAILED
test tests::invalid_email_returns_invalid ... FAILED
test tests::short_password_returns_invalid ... FAILED
```
Tests fail as expected (todo! panic).
## Step 4: Implement Minimal Code (GREEN)
```rust
pub fn validate_registration(request: &RegistrationRequest) -> ValidationResult {
let mut errors = Vec::new();
if request.name.trim().is_empty() {
errors.push("Name is required".into());
}
if !request.email.contains('@') {
errors.push("Invalid email format".into());
}
if request.password.len() < 8 {
errors.push("Password must be at least 8 characters".into());
}
if errors.is_empty() {
ValidationResult::Valid
} else {
ValidationResult::Invalid(errors)
}
}
```
## Step 5: Run Tests — Verify PASS
```bash
$ cargo test
running 4 tests
test tests::valid_registration_returns_valid ... ok
test tests::blank_name_returns_invalid ... ok
test tests::invalid_email_returns_invalid ... ok
test tests::short_password_returns_invalid ... ok
test result: ok. 4 passed; 0 failed
```
All tests passing!
## Step 6: Check Coverage
```bash
$ cargo llvm-cov
Coverage: 100.0% of lines
```
Coverage: 100%
## TDD Complete!
````
## 测试模式
### 单元测试
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_two_numbers() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn handles_error() -> Result<(), Box<dyn std::error::Error>> {
let result = parse_config(r#"port = 8080"#)?;
assert_eq!(result.port, 8080);
Ok(())
}
}
```
### 使用 rstest 进行参数化测试
```rust
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);
}
```
### 异步测试
```rust
#[tokio::test]
async fn fetches_data_successfully() {
let client = TestClient::new().await;
let result = client.get("/data").await;
assert!(result.is_ok());
}
```
### 基于属性的测试
```rust
use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip(input in ".*") {
let encoded = encode(&input);
let decoded = decode(&encoded).unwrap();
assert_eq!(input, decoded);
}
}
```
## 覆盖率命令
```bash
# Summary report
cargo llvm-cov
# HTML report
cargo llvm-cov --html
# Fail if below threshold
cargo llvm-cov --fail-under-lines 80
# Run specific test
cargo test test_name
# Run with output
cargo test -- --nocapture
# Run without stopping on first failure
cargo test --no-fail-fast
```
## 覆盖率目标
| 代码类型 | 目标 |
|-----------|--------|
| 关键业务逻辑 | 100% |
| 公共 API | 90%+ |
| 通用代码 | 80%+ |
| 生成的 / FFI 绑定 | 排除 |
## TDD 最佳实践
**应做:**
* **首先**编写测试,在任何实现之前
* 每次更改后运行测试
* 使用 `assert_eq!` 而非 `assert!` 以获得更好的错误信息
* 在返回 `Result` 的测试中使用 `?` 以获得更清晰的输出
* 测试行为,而非实现
* 包含边界情况(空值、边界值、错误路径)
**不应做:**
* 在测试之前编写实现
* 跳过 RED 阶段
* 在 `Result::is_err()` 可用时使用 `#[should_panic]`
* 在测试中使用 `sleep()` — 应使用通道或 `tokio::time::pause()`
* 模拟一切 — 在可行时优先使用集成测试
## 相关命令
* `/rust-build` - 修复构建错误
* `/rust-review` - 在实现后审查代码
* `/verify` - 运行完整的验证循环
## 相关
* 技能:`skills/rust-testing/`
* 技能:`skills/rust-patterns/`