mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 11:23:32 +08:00
docs(zh-CN): sync Chinese docs with latest upstream changes
This commit is contained in:
153
docs/zh-CN/rules/rust/coding-style.md
Normal file
153
docs/zh-CN/rules/rust/coding-style.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.rs"
|
||||
---
|
||||
|
||||
# Rust 编码风格
|
||||
|
||||
> 本文档扩展了 [common/coding-style.md](../common/coding-style.md) 中关于 Rust 的特定内容。
|
||||
|
||||
## 格式化
|
||||
|
||||
* **rustfmt** 用于强制执行 — 提交前务必运行 `cargo fmt`
|
||||
* **clippy** 用于代码检查 — `cargo clippy -- -D warnings`(将警告视为错误)
|
||||
* 4 空格缩进(rustfmt 默认)
|
||||
* 最大行宽:100 个字符(rustfmt 默认)
|
||||
|
||||
## 不可变性
|
||||
|
||||
Rust 变量默认是不可变的 — 请遵循此原则:
|
||||
|
||||
* 默认使用 `let`;仅在需要修改时才使用 `let mut`
|
||||
* 优先返回新值,而非原地修改
|
||||
* 当函数可能分配内存也可能不分配时,使用 `Cow<'_, T>`
|
||||
|
||||
```rust
|
||||
use std::borrow::Cow;
|
||||
|
||||
// GOOD — immutable by default, new value returned
|
||||
fn normalize(input: &str) -> Cow<'_, str> {
|
||||
if input.contains(' ') {
|
||||
Cow::Owned(input.replace(' ', "_"))
|
||||
} else {
|
||||
Cow::Borrowed(input)
|
||||
}
|
||||
}
|
||||
|
||||
// BAD — unnecessary mutation
|
||||
fn normalize_bad(input: &mut String) {
|
||||
*input = input.replace(' ', "_");
|
||||
}
|
||||
```
|
||||
|
||||
## 命名
|
||||
|
||||
遵循标准的 Rust 约定:
|
||||
|
||||
* `snake_case` 用于函数、方法、变量、模块、crate
|
||||
* `PascalCase`(大驼峰式)用于类型、特征、枚举、类型参数
|
||||
* `SCREAMING_SNAKE_CASE` 用于常量和静态变量
|
||||
* 生命周期:简短的小写字母(`'a`,`'de`)— 复杂情况使用描述性名称(`'input`)
|
||||
|
||||
## 所有权与借用
|
||||
|
||||
* 默认借用(`&T`);仅在需要存储或消耗时再获取所有权
|
||||
* 切勿在不理解根本原因的情况下,为了满足借用检查器而克隆数据
|
||||
* 在函数参数中,优先接受 `&str` 而非 `String`,优先接受 `&[T]` 而非 `Vec<T>`
|
||||
* 对于需要拥有 `String` 的构造函数,使用 `impl Into<String>`
|
||||
|
||||
```rust
|
||||
// GOOD — borrows when ownership isn't needed
|
||||
fn word_count(text: &str) -> usize {
|
||||
text.split_whitespace().count()
|
||||
}
|
||||
|
||||
// GOOD — takes ownership in constructor via Into
|
||||
fn new(name: impl Into<String>) -> Self {
|
||||
Self { name: name.into() }
|
||||
}
|
||||
|
||||
// BAD — takes String when &str suffices
|
||||
fn word_count_bad(text: String) -> usize {
|
||||
text.split_whitespace().count()
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
* 使用 `Result<T, E>` 和 `?` 进行传播 — 切勿在生产代码中使用 `unwrap()`
|
||||
* **库**:使用 `thiserror` 定义类型化错误
|
||||
* **应用程序**:使用 `anyhow` 以获取灵活的错误上下文
|
||||
* 使用 `.with_context(|| format!("failed to ..."))?` 添加上下文
|
||||
* 将 `unwrap()` / `expect()` 保留用于测试和真正无法到达的状态
|
||||
|
||||
```rust
|
||||
// GOOD — library error with thiserror
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("failed to read config: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("invalid config format: {0}")]
|
||||
Parse(String),
|
||||
}
|
||||
|
||||
// GOOD — application error with anyhow
|
||||
use anyhow::Context;
|
||||
|
||||
fn load_config(path: &str) -> anyhow::Result<Config> {
|
||||
let content = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read {path}"))?;
|
||||
toml::from_str(&content)
|
||||
.with_context(|| format!("failed to parse {path}"))
|
||||
}
|
||||
```
|
||||
|
||||
## 迭代器优于循环
|
||||
|
||||
对于转换操作,优先使用迭代器链;对于复杂的控制流,使用循环:
|
||||
|
||||
```rust
|
||||
// GOOD — declarative and composable
|
||||
let active_emails: Vec<&str> = users.iter()
|
||||
.filter(|u| u.is_active)
|
||||
.map(|u| u.email.as_str())
|
||||
.collect();
|
||||
|
||||
// GOOD — loop for complex logic with early returns
|
||||
for user in &users {
|
||||
if let Some(verified) = verify_email(&user.email)? {
|
||||
send_welcome(&verified)?;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 模块组织
|
||||
|
||||
按领域而非类型组织:
|
||||
|
||||
```text
|
||||
src/
|
||||
├── main.rs
|
||||
├── lib.rs
|
||||
├── auth/ # Domain module
|
||||
│ ├── mod.rs
|
||||
│ ├── token.rs
|
||||
│ └── middleware.rs
|
||||
├── orders/ # Domain module
|
||||
│ ├── mod.rs
|
||||
│ ├── model.rs
|
||||
│ └── service.rs
|
||||
└── db/ # Infrastructure
|
||||
├── mod.rs
|
||||
└── pool.rs
|
||||
```
|
||||
|
||||
## 可见性
|
||||
|
||||
* 默认为私有;使用 `pub(crate)` 进行内部共享
|
||||
* 仅将属于 crate 公共 API 的部分标记为 `pub`
|
||||
* 从 `lib.rs` 重新导出公共 API
|
||||
|
||||
## 参考
|
||||
|
||||
有关全面的 Rust 惯用法和模式,请参阅技能:`rust-patterns`。
|
||||
17
docs/zh-CN/rules/rust/hooks.md
Normal file
17
docs/zh-CN/rules/rust/hooks.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.rs"
|
||||
- "**/Cargo.toml"
|
||||
---
|
||||
|
||||
# Rust 钩子
|
||||
|
||||
> 此文件扩展了 [common/hooks.md](../common/hooks.md),包含 Rust 特定内容。
|
||||
|
||||
## PostToolUse 钩子
|
||||
|
||||
在 `~/.claude/settings.json` 中配置:
|
||||
|
||||
* **cargo fmt**:编辑后自动格式化 `.rs` 文件
|
||||
* **cargo clippy**:编辑 Rust 文件后运行 lint 检查
|
||||
* **cargo check**:更改后验证编译(比 `cargo build` 更快)
|
||||
169
docs/zh-CN/rules/rust/patterns.md
Normal file
169
docs/zh-CN/rules/rust/patterns.md
Normal file
@@ -0,0 +1,169 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.rs"
|
||||
---
|
||||
|
||||
# Rust 设计模式
|
||||
|
||||
> 本文档在 [common/patterns.md](../common/patterns.md) 的基础上,补充了 Rust 特有的内容。
|
||||
|
||||
## 基于 Trait 的 Repository 模式
|
||||
|
||||
将数据访问封装在 trait 之后:
|
||||
|
||||
```rust
|
||||
pub trait OrderRepository: Send + Sync {
|
||||
fn find_by_id(&self, id: u64) -> Result<Option<Order>, StorageError>;
|
||||
fn find_all(&self) -> Result<Vec<Order>, StorageError>;
|
||||
fn save(&self, order: &Order) -> Result<Order, StorageError>;
|
||||
fn delete(&self, id: u64) -> Result<(), StorageError>;
|
||||
}
|
||||
```
|
||||
|
||||
具体的实现负责处理存储细节(如 Postgres、SQLite,或用于测试的内存存储)。
|
||||
|
||||
## 服务层
|
||||
|
||||
业务逻辑位于服务结构体中;通过构造函数注入依赖:
|
||||
|
||||
```rust
|
||||
pub struct OrderService {
|
||||
repo: Box<dyn OrderRepository>,
|
||||
payment: Box<dyn PaymentGateway>,
|
||||
}
|
||||
|
||||
impl OrderService {
|
||||
pub fn new(repo: Box<dyn OrderRepository>, payment: Box<dyn PaymentGateway>) -> Self {
|
||||
Self { repo, payment }
|
||||
}
|
||||
|
||||
pub fn place_order(&self, request: CreateOrderRequest) -> anyhow::Result<OrderSummary> {
|
||||
let order = Order::from(request);
|
||||
self.payment.charge(order.total())?;
|
||||
let saved = self.repo.save(&order)?;
|
||||
Ok(OrderSummary::from(saved))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 为类型安全使用 Newtype 模式
|
||||
|
||||
使用不同的包装类型防止参数混淆:
|
||||
|
||||
```rust
|
||||
struct UserId(u64);
|
||||
struct OrderId(u64);
|
||||
|
||||
fn get_order(user: UserId, order: OrderId) -> anyhow::Result<Order> {
|
||||
// Can't accidentally swap user and order IDs at call sites
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
## 枚举状态机
|
||||
|
||||
将状态建模为枚举 —— 使非法状态无法表示:
|
||||
|
||||
```rust
|
||||
enum ConnectionState {
|
||||
Disconnected,
|
||||
Connecting { attempt: u32 },
|
||||
Connected { session_id: String },
|
||||
Failed { reason: String, retries: u32 },
|
||||
}
|
||||
|
||||
fn handle(state: &ConnectionState) {
|
||||
match state {
|
||||
ConnectionState::Disconnected => connect(),
|
||||
ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),
|
||||
ConnectionState::Connecting { .. } => wait(),
|
||||
ConnectionState::Connected { session_id } => use_session(session_id),
|
||||
ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),
|
||||
ConnectionState::Failed { reason, .. } => log_failure(reason),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
始终进行穷尽匹配 —— 对于业务关键的枚举,不要使用通配符 `_`。
|
||||
|
||||
## 建造者模式
|
||||
|
||||
适用于具有多个可选参数的结构体:
|
||||
|
||||
```rust
|
||||
pub struct ServerConfig {
|
||||
host: String,
|
||||
port: u16,
|
||||
max_connections: usize,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
pub fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {
|
||||
ServerConfigBuilder {
|
||||
host: host.into(),
|
||||
port,
|
||||
max_connections: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServerConfigBuilder {
|
||||
host: String,
|
||||
port: u16,
|
||||
max_connections: usize,
|
||||
}
|
||||
|
||||
impl ServerConfigBuilder {
|
||||
pub fn max_connections(mut self, n: usize) -> Self {
|
||||
self.max_connections = n;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ServerConfig {
|
||||
ServerConfig {
|
||||
host: self.host,
|
||||
port: self.port,
|
||||
max_connections: self.max_connections,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 密封 Trait 以控制扩展性
|
||||
|
||||
使用私有模块来密封一个 trait,防止外部实现:
|
||||
|
||||
```rust
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
}
|
||||
|
||||
pub trait Format: private::Sealed {
|
||||
fn encode(&self, data: &[u8]) -> Vec<u8>;
|
||||
}
|
||||
|
||||
pub struct Json;
|
||||
impl private::Sealed for Json {}
|
||||
impl Format for Json {
|
||||
fn encode(&self, data: &[u8]) -> Vec<u8> { todo!() }
|
||||
}
|
||||
```
|
||||
|
||||
## API 响应包装器
|
||||
|
||||
使用泛型枚举实现一致的 API 响应:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
#[serde(tag = "status")]
|
||||
pub enum ApiResponse<T: serde::Serialize> {
|
||||
#[serde(rename = "ok")]
|
||||
Ok { data: T },
|
||||
#[serde(rename = "error")]
|
||||
Error { message: String },
|
||||
}
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
参见技能:`rust-patterns`,其中包含全面的模式,涵盖所有权、trait、泛型、并发和异步。
|
||||
142
docs/zh-CN/rules/rust/security.md
Normal file
142
docs/zh-CN/rules/rust/security.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.rs"
|
||||
---
|
||||
|
||||
# Rust 安全
|
||||
|
||||
> 本文档在 [common/security.md](../common/security.md) 的基础上扩展了 Rust 相关的内容。
|
||||
|
||||
## 密钥管理
|
||||
|
||||
* 切勿在源代码中硬编码 API 密钥、令牌或凭证
|
||||
* 使用环境变量:`std::env::var("API_KEY")`
|
||||
* 如果启动时缺少必需的密钥,应快速失败
|
||||
* 将 `.env` 文件保存在 `.gitignore` 中
|
||||
|
||||
```rust
|
||||
// BAD
|
||||
const API_KEY: &str = "sk-abc123...";
|
||||
|
||||
// GOOD — environment variable with early validation
|
||||
fn load_api_key() -> anyhow::Result<String> {
|
||||
std::env::var("PAYMENT_API_KEY")
|
||||
.context("PAYMENT_API_KEY must be set")
|
||||
}
|
||||
```
|
||||
|
||||
## SQL 注入防护
|
||||
|
||||
* 始终使用参数化查询 —— 切勿将用户输入格式化到 SQL 字符串中
|
||||
* 使用支持绑定参数的查询构建器或 ORM(sqlx, diesel, sea-orm)
|
||||
|
||||
```rust
|
||||
// BAD — SQL injection via format string
|
||||
let query = format!("SELECT * FROM users WHERE name = '{name}'");
|
||||
sqlx::query(&query).fetch_one(&pool).await?;
|
||||
|
||||
// GOOD — parameterized query with sqlx
|
||||
// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1
|
||||
sqlx::query("SELECT * FROM users WHERE name = $1")
|
||||
.bind(&name)
|
||||
.fetch_one(&pool)
|
||||
.await?;
|
||||
```
|
||||
|
||||
## 输入验证
|
||||
|
||||
* 在处理之前,在系统边界处验证所有用户输入
|
||||
* 利用类型系统来强制约束(newtype 模式)
|
||||
* 进行解析,而非验证 —— 在边界处将非结构化数据转换为有类型的结构体
|
||||
* 以清晰的错误信息拒绝无效输入
|
||||
|
||||
```rust
|
||||
// Parse, don't validate — invalid states are unrepresentable
|
||||
pub struct Email(String);
|
||||
|
||||
impl Email {
|
||||
pub fn parse(input: &str) -> Result<Self, ValidationError> {
|
||||
let trimmed = input.trim();
|
||||
let at_pos = trimmed.find('@')
|
||||
.filter(|&p| p > 0 && p < trimmed.len() - 1)
|
||||
.ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;
|
||||
let domain = &trimmed[at_pos + 1..];
|
||||
if trimmed.len() > 254 || !domain.contains('.') {
|
||||
return Err(ValidationError::InvalidEmail(input.to_string()));
|
||||
}
|
||||
// For production use, prefer a validated email crate (e.g., `email_address`)
|
||||
Ok(Self(trimmed.to_string()))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 不安全代码
|
||||
|
||||
* 尽量减少 `unsafe` 块 —— 优先使用安全的抽象
|
||||
* 每个 `unsafe` 块必须附带一个 `// SAFETY:` 注释来解释其不变量
|
||||
* 切勿为了方便而使用 `unsafe` 来绕过借用检查器
|
||||
* 在代码审查时审核所有 `unsafe` 代码 —— 若无合理解释,应视为危险信号
|
||||
* 优先使用 `safe` 作为 C 库的 FFI 包装器
|
||||
|
||||
```rust
|
||||
// GOOD — safety comment documents ALL required invariants
|
||||
let widget: &Widget = {
|
||||
// SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,
|
||||
// and no mutable references or mutations exist for its lifetime.
|
||||
unsafe { &*ptr }
|
||||
};
|
||||
|
||||
// BAD — no safety justification
|
||||
unsafe { &*ptr }
|
||||
```
|
||||
|
||||
## 依赖项安全
|
||||
|
||||
* 运行 `cargo audit` 以扫描依赖项中已知的 CVE
|
||||
* 运行 `cargo deny check` 以确保许可证和公告合规
|
||||
* 使用 `cargo tree` 来审计传递依赖项
|
||||
* 保持依赖项更新 —— 设置 Dependabot 或 Renovate
|
||||
* 最小化依赖项数量 —— 添加新 crate 前进行评估
|
||||
|
||||
```bash
|
||||
# Security audit
|
||||
cargo audit
|
||||
|
||||
# Deny advisories, duplicate versions, and restricted licenses
|
||||
cargo deny check
|
||||
|
||||
# Inspect dependency tree
|
||||
cargo tree
|
||||
cargo tree -d # Show duplicates only
|
||||
```
|
||||
|
||||
## 错误信息
|
||||
|
||||
* 切勿在 API 响应中暴露内部路径、堆栈跟踪或数据库错误
|
||||
* 在服务器端记录详细错误;向客户端返回通用消息
|
||||
* 使用 `tracing` 或 `log` 进行结构化的服务器端日志记录
|
||||
|
||||
```rust
|
||||
// Map errors to appropriate status codes and generic messages
|
||||
// (Example uses axum; adapt the response type to your framework)
|
||||
match order_service.find_by_id(id) {
|
||||
Ok(order) => Ok((StatusCode::OK, Json(order))),
|
||||
Err(ServiceError::NotFound(_)) => {
|
||||
tracing::info!(order_id = id, "order not found");
|
||||
Err((StatusCode::NOT_FOUND, "Resource not found"))
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(order_id = id, error = %e, "unexpected error");
|
||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
关于不安全代码指南和所有权模式,请参见技能:`rust-patterns`。
|
||||
关于通用安全检查清单,请参见技能:`security-review`。
|
||||
156
docs/zh-CN/rules/rust/testing.md
Normal file
156
docs/zh-CN/rules/rust/testing.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
paths:
|
||||
- "**/*.rs"
|
||||
---
|
||||
|
||||
# Rust 测试
|
||||
|
||||
> 本文件扩展了 [common/testing.md](../common/testing.md) 中关于 Rust 的特定内容。
|
||||
|
||||
## 测试框架
|
||||
|
||||
* **`#[test]`** 配合 `#[cfg(test)]` 模块进行单元测试
|
||||
* **rstest** 用于参数化测试和夹具
|
||||
* **proptest** 用于基于属性的测试
|
||||
* **mockall** 用于基于特征的模拟
|
||||
* **`#[tokio::test]`** 用于异步测试
|
||||
|
||||
## 测试组织
|
||||
|
||||
```text
|
||||
my_crate/
|
||||
├── src/
|
||||
│ ├── lib.rs # Unit tests in #[cfg(test)] modules
|
||||
│ ├── auth/
|
||||
│ │ └── mod.rs # #[cfg(test)] mod tests { ... }
|
||||
│ └── orders/
|
||||
│ └── service.rs # #[cfg(test)] mod tests { ... }
|
||||
├── tests/ # Integration tests (each file = separate binary)
|
||||
│ ├── api_test.rs
|
||||
│ ├── db_test.rs
|
||||
│ └── common/ # Shared test utilities
|
||||
│ └── mod.rs
|
||||
└── benches/ # Criterion benchmarks
|
||||
└── benchmark.rs
|
||||
```
|
||||
|
||||
单元测试放在同一文件的 `#[cfg(test)]` 模块内。集成测试放在 `tests/` 目录中。
|
||||
|
||||
## 单元测试模式
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn creates_user_with_valid_email() {
|
||||
let user = User::new("Alice", "alice@example.com").unwrap();
|
||||
assert_eq!(user.name, "Alice");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_email() {
|
||||
let result = User::new("Bob", "not-an-email");
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("invalid email"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 参数化测试
|
||||
|
||||
```rust
|
||||
use rstest::rstest;
|
||||
|
||||
#[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());
|
||||
}
|
||||
```
|
||||
|
||||
## 使用 mockall 进行模拟
|
||||
|
||||
在生产代码中定义特征;在测试模块中生成模拟对象:
|
||||
|
||||
```rust
|
||||
// Production trait — pub so integration tests can import it
|
||||
pub trait UserRepository {
|
||||
fn find_by_id(&self, id: u64) -> Option<User>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use mockall::predicate::eq;
|
||||
|
||||
mockall::mock! {
|
||||
pub Repo {}
|
||||
impl UserRepository for Repo {
|
||||
fn find_by_id(&self, id: u64) -> Option<User>;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_returns_user_when_found() {
|
||||
let mut mock = MockRepo::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");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试命名
|
||||
|
||||
使用描述性的名称来解释场景:
|
||||
|
||||
* `creates_user_with_valid_email()`
|
||||
* `rejects_order_when_insufficient_stock()`
|
||||
* `returns_none_when_not_found()`
|
||||
|
||||
## 覆盖率
|
||||
|
||||
* 目标为 80%+ 的行覆盖率
|
||||
* 使用 **cargo-llvm-cov** 生成覆盖率报告
|
||||
* 关注业务逻辑 —— 排除生成的代码和 FFI 绑定
|
||||
|
||||
```bash
|
||||
cargo llvm-cov # Summary
|
||||
cargo llvm-cov --html # HTML report
|
||||
cargo llvm-cov --fail-under-lines 80 # Fail if below threshold
|
||||
```
|
||||
|
||||
## 测试命令
|
||||
|
||||
```bash
|
||||
cargo test # Run all tests
|
||||
cargo test -- --nocapture # Show println output
|
||||
cargo test test_name # Run tests matching pattern
|
||||
cargo test --lib # Unit tests only
|
||||
cargo test --test api_test # Specific integration test (tests/api_test.rs)
|
||||
cargo test --doc # Doc tests only
|
||||
```
|
||||
|
||||
## 参考
|
||||
|
||||
有关全面的测试模式(包括基于属性的测试、夹具以及使用 Criterion 进行基准测试),请参阅技能:`rust-testing`。
|
||||
Reference in New Issue
Block a user