Version: 1.0.0 Last Updated: 2025-11-26 Priority: P0 Level Purpose: High-quality code implementation through test-driven development
Overview
The complete TDD (Test-Driven Development) workflow in the Miyabi project. Through the Red-Green-Refactor cycle, it achieves robust and highly maintainable code.
Invocation Triggers
| Trigger | Example |
|---|---|
| New feature implementation | "implement feature X", "add new functionality" |
| Bug fix | "fix bug", "resolve issue" |
| Adding tests | "add tests", "write tests for" |
| Refactoring | "refactor with tests", "improve code" |
| TDD request | "use TDD", "test-driven" |
TDD Cycle: Red-Green-Refactor
graph LR
RED[RED<br/>失敗するテスト作成] --> GREEN[GREEN<br/>最小実装で成功]
GREEN --> REFACTOR[REFACTOR<br/>コード改善]
REFACTOR --> RED
style RED fill:#ff6b6b,stroke:#333,color:#fff
style GREEN fill:#51cf66,stroke:#333,color:#fff
style REFACTOR fill:#339af0,stroke:#333,color:#fff
Phase 1: RED (Write a failing test)
Purpose: Codify the specification of the feature to be implemented as a test
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_feature_basic() {
// Arrange: テスト準備
let input = "test_input";
// Act: 実行
let result = new_feature(input);
// Assert: 検証
assert_eq!(result, expected_output);
}
#[test]
fn test_new_feature_edge_case() {
// エッジケースのテスト
let result = new_feature("");
assert!(result.is_err());
}
}
Execution:
cargo test test_new_feature --no-run && cargo test test_new_feature
# 期待: FAILED (テストが失敗することを確認)
Phase 2: GREEN (Minimal implementation to pass the test)
Purpose: Implement the minimum code needed to pass the test
pub fn new_feature(input: &str) -> Result<String, Error> {
// 最小限の実装
if input.is_empty() {
return Err(Error::InvalidInput);
}
Ok(expected_output.to_string())
}
Execution:
cargo test test_new_feature
# 期待: PASSED (全テスト成功)
Phase 3: REFACTOR (Code improvement)
Purpose: Improve code quality while keeping tests passing
pub fn new_feature(input: &str) -> Result<String, Error> {
// バリデーション分離
validate_input(input)?;
// 処理の明確化
let processed = process_input(input);
// 結果構築
Ok(build_output(processed))
}
fn validate_input(input: &str) -> Result<(), Error> {
if input.is_empty() {
return Err(Error::InvalidInput);
}
Ok(())
}
Execution:
cargo test test_new_feature && cargo clippy && cargo fmt -- --check
# 期待: 全テスト成功 + 警告なし + フォーマット済み
Test Pyramid
/\
/ \ E2E Tests (10%)
/----\ - 完全なユーザーフロー
/ \ - 本番環境に近い状態
/--------\
/ \ Integration Tests (30%)
/------------\ - コンポーネント間連携
/ \ - 外部API模擬
/----------------\
/ \ Unit Tests (60%)
/--------------------\ - 関数・メソッド単位
- 高速・独立・決定的
Guidelines for each level
| Level | Coverage target | Execution time | Frequency |
|---|---|---|---|
| Unit | 80%+ | < 1 second/test | Always |
| Integration | Key paths | < 10 seconds/test | CI |
| E2E | Critical flows | < 60 seconds/test | Daily |
Rust TDD Pattern Collection
Pattern 1: Testing the Result type
#[test]
fn test_operation_success() {
let result = operation(valid_input);
assert!(result.is_ok());
assert_eq!(result.unwrap(), expected);
}
#[test]
fn test_operation_error() {
let result = operation(invalid_input);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::InvalidInput));
}
Pattern 2: Testing async functions
#[tokio::test]
async fn test_async_operation() {
let result = async_operation().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_async_with_timeout() {
let result = tokio::time::timeout(
Duration::from_secs(5),
async_operation()
).await;
assert!(result.is_ok());
}
Pattern 3: Using mocks
use mockall::predicate::*;
use mockall::mock;
mock! {
pub ExternalService {
async fn call(&self, input: &str) -> Result<String, Error>;
}
}
#[tokio::test]
async fn test_with_mock() {
let mut mock = MockExternalService::new();
mock.expect_call()
.with(eq("test"))
.returning(|_| Ok("response".to_string()));
let result = my_function(&mock).await;
assert!(result.is_ok());
}
Pattern 4: Property-based testing
use proptest::prelude::*;
proptest! {
#[test]
fn test_property(input in ".*") {
let result = process(&input);
// 任意の入力に対して常に真な性質
prop_assert!(result.len() >= 0);
}
#[test]
fn test_roundtrip(input in any::<u32>()) {
let encoded = encode(input);
let decoded = decode(&encoded);
prop_assert_eq!(input, decoded);
}
}
Pattern 5: Test fixtures
struct TestFixture {
db: TestDatabase,
service: TestService,
}
impl TestFixture {
async fn new() -> Self {
let db = TestDatabase::setup().await;
let service = TestService::new(&db);
Self { db, service }
}
async fn teardown(self) {
self.db.cleanup().await;
}
}
#[tokio::test]
async fn test_with_fixture() {
let fixture = TestFixture::new().await;
// テスト実行
let result = fixture.service.operation().await;
assert!(result.is_ok());
fixture.teardown().await;
}
Pattern 6: Snapshot testing
use insta::assert_snapshot;
#[test]
fn test_output_format() {
let result = generate_output(input);
assert_snapshot!(result);
}
#[test]
fn test_json_output() {
let result = to_json(&data);
assert_snapshot!(result);
}
Command Reference
Basic test commands
# 全テスト実行
cargo test --workspace
# 特定パッケージのテスト
cargo test -p miyabi-agents
# 特定テストの実行
cargo test test_name
# テスト名のパターンマッチ
cargo test workflow_
# 並列度制御
cargo test -- --test-threads=1
# 出力表示
cargo test -- --nocapture
# 失敗時のみ出力
cargo test -- --show-output
Advanced test commands
# ドキュメントテスト
cargo test --doc
# 統合テストのみ
cargo test --test integration_test
# ベンチマーク
cargo bench
# カバレッジ (cargo-llvm-cov)
cargo llvm-cov --workspace --html
# プロパティテスト(長時間)
PROPTEST_CASES=10000 cargo test
Commands for CI
# フルチェック
cargo test --workspace --all-features && \
cargo clippy --workspace --all-targets --all-features -- -D warnings && \
cargo fmt --all -- --check
# カバレッジレポート
cargo llvm-cov --workspace --lcov --output-path lcov.info
Test Structure
Directory structure
crates/miyabi-xxx/
├── src/
│ ├── lib.rs
│ └── feature.rs # 機能コード
├── tests/
│ ├── integration_test.rs # 統合テスト
│ └── e2e_test.rs # E2Eテスト
└── benches/
└── benchmark.rs # ベンチマーク
Test module structure
// src/feature.rs
pub fn feature_function() -> Result<(), Error> {
// 実装
}
#[cfg(test)]
mod tests {
use super::*;
mod success_cases {
use super::*;
#[test]
fn test_basic_success() { }
#[test]
fn test_with_options() { }
}
mod error_cases {
use super::*;
#[test]
fn test_invalid_input() { }
#[test]
fn test_network_error() { }
}
mod edge_cases {
use super::*;
#[test]
fn test_empty_input() { }
#[test]
fn test_max_size() { }
}
}
Best Practices
DO (Recommended)
-
One assertion per test principle
#[test] fn test_single_assertion() { let result = operation(); assert_eq!(result, expected); } -
AAA pattern (Arrange-Act-Assert)
#[test] fn test_aaa_pattern() { // Arrange let input = setup_input(); // Act let result = operation(input); // Assert assert!(result.is_ok()); } -
Test names that express intent
#[test] fn when_input_is_empty_returns_validation_error() { } #[test] fn given_valid_user_when_login_then_returns_token() { } -
Make test data explicit
#[test] fn test_with_explicit_data() { let user = User { id: 1, name: "Test User".to_string(), email: "test@example.com".to_string(), }; // ... }
DON'T (Not Recommended)
-
Dependencies between tests
// BAD: テストの実行順序に依存 static mut SHARED_STATE: i32 = 0; -
Testing implementation details
// BAD: 内部構造に依存 assert_eq!(result.internal_cache.len(), 5); -
Non-deterministic tests
// BAD: 時間依存 assert!(Instant::now() > start_time); -
Overly complex setup
// BAD: 50行のセットアップコード
Coverage Guidelines
Coverage targets
| Component | Target | Priority |
|---|---|---|
| Core Types | 90%+ | P0 |
| Business Logic | 85%+ | P0 |
| API Handlers | 80%+ | P1 |
| Utilities | 70%+ | P2 |
| CLI | 60%+ | P2 |
Coverage measurement
# インストール
cargo install cargo-llvm-cov
# 測定実行
cargo llvm-cov --workspace --html
# レポート確認
open target/llvm-cov/html/index.html
Coverage exclusion
// カバレッジから除外
#[cfg(not(tarpaulin_include))]
fn debug_only_function() { }
// または
#[coverage(off)]
fn uncoverable_function() { }
CI/CD Integration
GitHub Actions configuration
name: TDD Workflow
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-action@stable
- name: Run Tests
run: |
cargo test --workspace --all-features
- name: Check Clippy
run: |
cargo clippy --workspace --all-targets -- -D warnings
- name: Check Format
run: |
cargo fmt --all -- --check
- name: Coverage
run: |
cargo llvm-cov --workspace --lcov --output-path lcov.info
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: lcov.info
Troubleshooting
Tests are flaky
Symptom: The same test sometimes fails
Remedy:
# 並列実行を無効化
cargo test -- --test-threads=1
# 詳細ログ
RUST_LOG=debug cargo test
# 特定テストを100回実行
for i in {1..100}; do cargo test flaky_test || exit 1; done
Tests are slow
Symptom: Test execution takes a long time
Remediation:
# コンパイル時間の確認
cargo build --timings
# テスト時間の計測
cargo test -- -Z unstable-options --report-time
# 並列度調整
cargo test -- --test-threads=8
Mocks do not work
Symptoms: Mocks are not called
Remediation:
// expect_*のチェック
mock.checkpoint(); // 期待した呼び出しを検証
// より具体的なマッチャー
mock.expect_call()
.with(eq("specific_input"))
.times(1)
.returning(|_| Ok(()));
Success Criteria
| Check Items | Criteria |
|---|---|
| Unit Tests | 100% pass |
| Integration Tests | 100% pass |
| Coverage | > 80% |
| No Warnings | cargo clippy clean |
| Formatted | cargo fmt clean |
Output Format
TDD Workflow Results
RED Phase:
New tests written: 5
Tests failing: 5 (expected)
GREEN Phase:
Implementation: Complete
Tests passing: 5/5
REFACTOR Phase:
Code improved: Yes
Tests still passing: 5/5
Coverage: 87.3%
Clippy: 0 warnings
Format: Clean
Ready to commit
Related Documentation
| Documentation | Purpose |
|---|---|
context/rust.md |
Rust Development Guidelines |
Skills/rust-development/ |
Rust Build Workflow |
Skills/debugging-troubleshooting/ |
Debug Support |
Related Skills
- Rust Development: Build/test execution
- Debugging Troubleshooting: Debugging when tests fail
- Git Workflow: Test check before commit
- Security Audit: Security testing