| name | rust-testing |
| description | Rust 測試技能。涵蓋 #[test] 單元測試、#[cfg(test)] 模組組織、 integration tests 目錄結構、assert! 系列巨集、測試 helper 函式、 proptest/quickcheck 屬性測試、mockall mocking 策略、 cargo-llvm-cov 覆蓋率報告、#[should_panic] 與 Result<()> 測試模式、 async 測試(tokio::test)、test fixtures 模式、cargo-nextest 加速。 觸發關鍵詞:Rust test, #[test], cargo test, integration test, proptest, mockall, coverage, cargo-llvm-cov, should_panic, test module, TDD Rust, cargo-nextest, assertion, test fixture
|
Rust Testing
適用場景
- 為 Rust 函式、模組、crate 撰寫單元測試與整合測試
- 使用 TDD(紅 → 綠 → 重構)流程開發新功能或修復 bug
- 以 proptest / quickcheck 進行屬性測試,自動產生邊界案例
- 以 mockall 建立 trait mock,隔離外部依賴
- 以 cargo-llvm-cov 產生覆蓋率報告,確保 ≥80% 行覆蓋
- 以 cargo-nextest 加速大量測試的平行執行
- 撰寫 async 測試(tokio::test / async-std)
- 撰寫 doc tests 確保文件範例可編譯執行
核心知識
測試分類
| 類別 | 位置 | 編譯方式 | 存取範圍 |
|---|
| 單元測試 | src/ 內 #[cfg(test)] mod tests | 與 crate 同一編譯單元 | 可存取 pub(crate) 與私有項目 |
| 整合測試 | tests/*.rs 或 tests/*/main.rs | 獨立 crate,僅連結公開 API | 僅 pub 項目 |
| 文件測試 | /// doc comment 中的 ````rust` 區塊 | 獨立編譯 | 僅 pub 項目 |
測試組織結構
my_crate/
├── src/
│ ├── lib.rs # pub fn + #[cfg(test)] mod tests
│ └── utils.rs # 同上
├── tests/
│ ├── smoke.rs # 獨立整合測試檔
│ └── api/
│ ├── main.rs # 多檔整合測試入口
│ └── helpers.rs # 共用 helper(不會被當成獨立測試)
└── Cargo.toml
assert 巨集對照
| 巨集 | 用途 | 失敗時輸出 |
|---|
assert!(expr) | 布林值為 true | assertion failed: expr |
assert_eq!(left, right) | 左右相等(需 PartialEq + Debug) | 顯示 left / right 差異 |
assert_ne!(left, right) | 左右不等 | 顯示 left / right |
debug_assert! | 僅 debug build 檢查 | 同 assert!,release 移除 |
自訂訊息:assert!(x > 0, "x 必須為正數,實際值: {x}");
#[cfg(test)] vs tests/ 目錄
#[cfg(test)]:測試專用程式碼只在 cargo test 時編譯,不進入 release binary。
tests/ 目錄:每個 .rs 被編譯為獨立 crate,適合驗證公開 API 的端對端行為。
- 子目錄
tests/common/mod.rs 或 tests/xxx/main.rs 不會被視為獨立測試入口。
cargo test 常用參數
cargo test
cargo test parse
cargo test --test smoke
cargo test -- --nocapture
cargo test -- --test-threads=1
cargo test --doc
cargo test -- --ignored
cargo nextest run
TDD 流程(Rust 實踐版)
- RED — 先寫測試,
cargo test 確認失敗
- GREEN — 寫最小實作讓測試通過
- REFACTOR — 改善結構,保持測試綠燈
- COVERAGE —
cargo llvm-cov --html 確認 ≥80%
程式碼範例
Basic — 單元測試基礎
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
return Err("除數不可為零".into());
}
Ok(a / b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide_ok() {
let result = divide(10.0, 2.0).unwrap();
assert_eq!(result, 5.0);
}
#[test]
fn test_divide_positive() {
let result = divide(6.0, 3.0).unwrap();
assert!(result > 0.0, "結果應為正數,實際: {result}");
}
#[test]
fn test_divide_result() -> Result<(), String> {
let val = divide(9.0, 3.0)?;
assert_eq!(val, 3.0);
Ok(())
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_index_panic() {
let v: Vec<i32> = vec![1, 2, 3];
let _ = v[99];
}
#[test]
#[ignore = "需要外部 API,CI 上跳過"]
fn test_slow_external() {
}
}
完整範例見 examples/basic.rs
Intermediate — mockall + proptest
use mockall::automock;
#[automock]
pub trait UserRepo {
fn find_by_id(&self, id: u64) -> Option<String>;
}
pub fn greet_user(repo: &dyn UserRepo, id: u64) -> String {
match repo.find_by_id(id) {
Some(name) => format!("你好, {name}!"),
None => "使用者不存在".into(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_greet_existing_user() {
let mut mock = MockUserRepo::new();
mock.expect_find_by_id()
.with(mockall::predicate::eq(42))
.times(1)
.returning(|_| Some("Alice".into()));
assert_eq!(greet_user(&mock, 42), "你好, Alice!");
}
}
use proptest::prelude::*;
fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
xs.iter().rev().cloned().collect()
}
proptest! {
#[test]
fn test_reverse_involution(ref v in prop::collection::vec(any::<i32>(), 0..100)) {
assert_eq!(reverse(&reverse(v)), *v);
}
#[test]
fn test_reverse_length(ref v in prop::collection::vec(any::<i32>(), 0..100)) {
assert_eq!(reverse(v).len(), v.len());
}
}
完整範例見 examples/intermediate.rs
Advanced — async 測試 + rstest fixtures + 整合測試策略
#[cfg(test)]
mod async_tests {
use tokio::time::{sleep, Duration};
async fn fetch_data(url: &str) -> Result<String, String> {
if url.is_empty() {
return Err("URL 不可為空".into());
}
sleep(Duration::from_millis(10)).await;
Ok(format!("回應自 {url}"))
}
#[tokio::test]
async fn test_fetch_ok() {
let result = fetch_data("https://example.com").await.unwrap();
assert!(result.contains("example.com"));
}
#[tokio::test]
async fn test_fetch_empty_url() {
let err = fetch_data("").await.unwrap_err();
assert_eq!(err, "URL 不可為空");
}
}
use rstest::rstest;
#[rstest]
#[case(2, 3, 5)]
#[case(0, 0, 0)]
#[case(-1, 1, 0)]
fn test_add(#[case] a: i32, #[case] b: i32, #[case] expected: i32) {
assert_eq!(a + b, expected);
}
完整範例見 examples/advanced.rs
常見錯誤對照表
| 錯誤訊息 | 原因 | 修復方式 |
|---|
test result: FAILED. 0 passed; 0 filtered out 但測試函式存在 | 函式缺少 #[test] 屬性標註 | 在 fn 前加上 #[test] |
cannot find value 'MockXxx' in this scope | 未匯入 mockall 產生的 mock 型別 | 確認 #[automock] 標註在 trait 上,且 use super::*; 正確匯入 |
the trait bound 'Xxx: Debug' is not satisfied | assert_eq! 需要 Debug trait | 為型別加上 #[derive(Debug)] |
thread 'test' panicked at 'assertion failed: ...' 且訊息不明 | 使用 assert! 但無自訂訊息 | 改用 assert_eq! 或加上第二參數自訂訊息 |
linking with 'cc' failed 在整合測試 | 整合測試試圖存取私有函式 | 將需要測試的函式改為 pub,或將測試移至 #[cfg(test)] 模組 |
error: custom panics in tests are not reported ... (should_panic) | expected 字串與實際 panic 訊息不匹配 | 確認 #[should_panic(expected = "...")] 內容為 panic 訊息的子字串 |
Cannot start a runtime from within a runtime | 在 #[tokio::test] 內再建 Runtime::new() | 移除手動建立的 runtime,直接使用 async fn + .await |
proptest too many shrink steps | 產生的測試案例空間過大,收縮超時 | 縮小策略範圍,例如 0..1000 改為 0..100 |
Cargo.toml 依賴模板
[package]
name = "my_crate"
version = "0.1.0"
edition = "2024"
[dependencies]
[dev-dependencies]
proptest = "1.6"
mockall = "0.13"
rstest = "0.23"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
覆蓋率指令
rustup component add llvm-tools-preview
cargo install cargo-llvm-cov
cargo llvm-cov
cargo llvm-cov --html
cargo llvm-cov nextest
cargo llvm-cov --fail-under-lines 80
cargo-nextest 指令
cargo install cargo-nextest
cargo nextest run
cargo nextest run -E 'test(parse)'
cargo nextest run --retries 2
cargo nextest run --profile ci
參考來源
- The Rust Programming Language — Ch.11 Testing — 官方教材測試章節
- Rust by Example — Testing — 測試範例集錦
- proptest Book — 屬性測試完整教學
- mockall crate docs — Mock 框架 API 文件
- cargo-llvm-cov README — 覆蓋率工具文件
- cargo-nextest 官網 — 下一代測試執行器
- rstest crate docs — 參數化測試與 fixtures