mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-31 22:23:27 +08:00
312 lines
6.9 KiB
Markdown
312 lines
6.9 KiB
Markdown
---
|
||
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/`
|