rust-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRust Testing Core Knowledge
Rust 测试核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.rust
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 or
vitestjest - Java Projects - Use for Java testing
junit - Python Projects - Use for Python
pytest - Go Projects - Use skill
go-testing - E2E Browser Testing - Use Playwright or Selenium
- JavaScript/TypeScript项目 - 使用或
vitestjest - Java项目 - 使用进行Java测试
junit - Python项目 - 使用进行Python测试
pytest - 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
undefinedbash
undefinedRun 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
undefinedcargo test -- --ignored
undefinedAssertions
断言
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
undefinedtoml
undefinedCargo.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
undefinedtoml
undefinedCargo.toml
Cargo.toml
[dev-dependencies]
mockall = "0.12"
undefined[dev-dependencies]
mockall = "0.12"
undefinedBasic 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-Pattern | Why It's Bad | Solution |
|---|---|---|
| Testing private functions | Coupled to implementation | Test through public API |
| Not using #[should_panic] | Missing error validation | Test expected panics explicitly |
| Shared mutable state in tests | Flaky tests | Use test isolation |
| Not using Result<()> in tests | Can't use ? operator | Return Result<(), Error> |
| Ignoring async tests | Wrong runtime behavior | Use #[tokio::test] for async |
| Not benchmarking | Performance regressions | Use Criterion for benchmarks |
| Missing property tests | Edge cases missed | Use proptest for algorithms |
| 反模式 | 危害 | 解决方案 |
|---|---|---|
| 测试私有函数 | 与实现细节耦合 | 通过公开API进行测试 |
| 不使用#[should_panic] | 缺少错误验证 | 显式测试预期的恐慌场景 |
| 测试中使用共享可变状态 | 测试结果不稳定 | 确保测试隔离 |
| 测试中不使用Result<()> | 无法使用?运算符 | 返回Result<(), Error>类型 |
| 忽略异步测试 | 运行时行为错误 | 对异步测试使用#[tokio::test] |
| 不进行基准测试 | 可能出现性能回归 | 使用Criterion进行基准测试 |
| 缺少基于属性的测试 | 可能遗漏边缘情况 | 使用proptest为算法编写测试 |
Quick Troubleshooting
快速故障排除
| Problem | Likely Cause | Solution |
|---|---|---|
| "test result: FAILED. 0 passed" | Panic in test | Check panic message, add proper assertions |
| Async test timeout | Missing await or infinite loop | Ensure all futures are awaited |
| Mock expectation failed | Wrong method calls | Verify mockall expectations |
| "cannot find derive macro" | Missing dev-dependency | Add mockall to [dev-dependencies] |
| Benchmark not running | Wrong harness setting | Set harness = false in [[bench]] |
| Coverage tool crashes | Incompatible version | Use 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
- 单元测试
- 异步测试
- 模拟测试
- 高级模式