home / skills / shunsukehayashi / miyabi-claude-plugins / tdd-workflow

tdd-workflow skill

/miyabi-full/skills/tdd-workflow

This skill guides you through a test-driven development workflow for Miyabi, delivering reliable Rust features with red-green-refactor discipline.

npx playbooks add skill shunsukehayashi/miyabi-claude-plugins --skill tdd-workflow

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
14.0 KB
---
name: TDD Workflow
description: Test-Driven Development workflow for Miyabi. Red-Green-Refactor cycle with Rust-specific patterns. Use when implementing new features, fixing bugs, or writing tests.
allowed-tools: Bash, Read, Write, Edit, Grep, Glob
---

# Test-Driven Development (TDD) Workflow

**Version**: 1.0.0
**Last Updated**: 2025-11-26
**Priority**: P0 Level
**Purpose**: テスト駆動開発による高品質なコード実装

---

## 概要

MiyabiプロジェクトにおけるTDD(Test-Driven Development)の完全なワークフロー。
Red-Green-Refactorサイクルを通じて、堅牢で保守性の高いコードを実現します。

---

## 呼び出しトリガー

| トリガー | 例 |
|---------|-----|
| 新機能実装 | "implement feature X", "add new functionality" |
| バグ修正 | "fix bug", "resolve issue" |
| テスト追加 | "add tests", "write tests for" |
| リファクタリング | "refactor with tests", "improve code" |
| TDD依頼 | "use TDD", "test-driven" |

---

## TDDサイクル: Red-Green-Refactor

```mermaid
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 (失敗するテストを書く)

**目的**: 実装したい機能の仕様をテストとして明文化

```rust
#[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());
    }
}
```

**実行**:
```bash
cargo test test_new_feature --no-run && cargo test test_new_feature
# 期待: FAILED (テストが失敗することを確認)
```

### Phase 2: GREEN (テストを通す最小実装)

**目的**: テストを通すための最小限のコード実装

```rust
pub fn new_feature(input: &str) -> Result<String, Error> {
    // 最小限の実装
    if input.is_empty() {
        return Err(Error::InvalidInput);
    }
    Ok(expected_output.to_string())
}
```

**実行**:
```bash
cargo test test_new_feature
# 期待: PASSED (全テスト成功)
```

### Phase 3: REFACTOR (コード改善)

**目的**: テストを維持しながらコード品質を改善

```rust
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(())
}
```

**実行**:
```bash
cargo test test_new_feature && cargo clippy && cargo fmt -- --check
# 期待: 全テスト成功 + 警告なし + フォーマット済み
```

---

## テストピラミッド

```
          /\
         /  \        E2E Tests (10%)
        /----\       - 完全なユーザーフロー
       /      \      - 本番環境に近い状態
      /--------\
     /          \    Integration Tests (30%)
    /------------\   - コンポーネント間連携
   /              \  - 外部API模擬
  /----------------\
 /                  \ Unit Tests (60%)
/--------------------\ - 関数・メソッド単位
                       - 高速・独立・決定的
```

### 各レベルの目安

| レベル | カバレッジ目標 | 実行時間 | 頻度 |
|--------|--------------|---------|------|
| Unit | 80%+ | < 1秒/テスト | 常時 |
| Integration | Key paths | < 10秒/テスト | CI |
| E2E | Critical flows | < 60秒/テスト | 日次 |

---

## Rust TDD パターン集

### Pattern 1: Result型のテスト

```rust
#[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: async関数のテスト

```rust
#[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: モック使用

```rust
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: プロパティベーステスト

```rust
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: テストフィクスチャ

```rust
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: スナップショットテスト

```rust
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);
}
```

---

## コマンドリファレンス

### 基本テストコマンド

```bash
# 全テスト実行
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
```

### 高度なテストコマンド

```bash
# ドキュメントテスト
cargo test --doc

# 統合テストのみ
cargo test --test integration_test

# ベンチマーク
cargo bench

# カバレッジ (cargo-llvm-cov)
cargo llvm-cov --workspace --html

# プロパティテスト(長時間)
PROPTEST_CASES=10000 cargo test
```

### CI用コマンド

```bash
# フルチェック
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
```

---

## テスト構造

### ディレクトリ構成

```
crates/miyabi-xxx/
├── src/
│   ├── lib.rs
│   └── feature.rs         # 機能コード
├── tests/
│   ├── integration_test.rs # 統合テスト
│   └── e2e_test.rs         # E2Eテスト
└── benches/
    └── benchmark.rs        # ベンチマーク
```

### テストモジュール構成

```rust
// 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() { }
    }
}
```

---

## ベストプラクティス

### DO (推奨)

1. **1テスト1アサーション原則**
   ```rust
   #[test]
   fn test_single_assertion() {
       let result = operation();
       assert_eq!(result, expected);
   }
   ```

2. **AAA パターン (Arrange-Act-Assert)**
   ```rust
   #[test]
   fn test_aaa_pattern() {
       // Arrange
       let input = setup_input();

       // Act
       let result = operation(input);

       // Assert
       assert!(result.is_ok());
   }
   ```

3. **意図を表すテスト名**
   ```rust
   #[test]
   fn when_input_is_empty_returns_validation_error() { }

   #[test]
   fn given_valid_user_when_login_then_returns_token() { }
   ```

4. **テストデータの明示化**
   ```rust
   #[test]
   fn test_with_explicit_data() {
       let user = User {
           id: 1,
           name: "Test User".to_string(),
           email: "[email protected]".to_string(),
       };
       // ...
   }
   ```

### DON'T (非推奨)

1. **テスト間の依存**
   ```rust
   // BAD: テストの実行順序に依存
   static mut SHARED_STATE: i32 = 0;
   ```

2. **実装詳細のテスト**
   ```rust
   // BAD: 内部構造に依存
   assert_eq!(result.internal_cache.len(), 5);
   ```

3. **非決定的テスト**
   ```rust
   // BAD: 時間依存
   assert!(Instant::now() > start_time);
   ```

4. **過度に複雑なセットアップ**
   ```rust
   // BAD: 50行のセットアップコード
   ```

---

## カバレッジガイドライン

### カバレッジ目標

| コンポーネント | 目標 | 優先度 |
|--------------|------|--------|
| Core Types | 90%+ | P0 |
| Business Logic | 85%+ | P0 |
| API Handlers | 80%+ | P1 |
| Utilities | 70%+ | P2 |
| CLI | 60%+ | P2 |

### カバレッジ測定

```bash
# インストール
cargo install cargo-llvm-cov

# 測定実行
cargo llvm-cov --workspace --html

# レポート確認
open target/llvm-cov/html/index.html
```

### カバレッジ除外

```rust
// カバレッジから除外
#[cfg(not(tarpaulin_include))]
fn debug_only_function() { }

// または
#[coverage(off)]
fn uncoverable_function() { }
```

---

## CI/CD統合

### GitHub Actions設定

```yaml
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
```

---

## トラブルシューティング

### テストが不安定

**症状**: 同じテストが時々失敗する

**対処**:
```bash
# 並列実行を無効化
cargo test -- --test-threads=1

# 詳細ログ
RUST_LOG=debug cargo test

# 特定テストを100回実行
for i in {1..100}; do cargo test flaky_test || exit 1; done
```

### テストが遅い

**症状**: テスト実行に時間がかかる

**対処**:
```bash
# コンパイル時間の確認
cargo build --timings

# テスト時間の計測
cargo test -- -Z unstable-options --report-time

# 並列度調整
cargo test -- --test-threads=8
```

### モックが動作しない

**症状**: モックが呼び出されない

**対処**:
```rust
// expect_*のチェック
mock.checkpoint(); // 期待した呼び出しを検証

// より具体的なマッチャー
mock.expect_call()
    .with(eq("specific_input"))
    .times(1)
    .returning(|_| Ok(()));
```

---

## 成功基準

| チェック項目 | 基準 |
|-------------|------|
| Unit Tests | 100% pass |
| Integration Tests | 100% pass |
| Coverage | > 80% |
| No Warnings | cargo clippy clean |
| Formatted | cargo fmt clean |

### 出力フォーマット

```
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
```

---

## 関連ドキュメント

| ドキュメント | 用途 |
|-------------|------|
| `context/rust.md` | Rust開発ガイドライン |
| `Skills/rust-development/` | Rustビルドワークフロー |
| `Skills/debugging-troubleshooting/` | デバッグ支援 |

---

## 関連Skills

- **Rust Development**: ビルド・テスト実行
- **Debugging Troubleshooting**: テスト失敗時のデバッグ
- **Git Workflow**: コミット前のテスト確認
- **Security Audit**: セキュリティテスト

Overview

This skill implements a Test-Driven Development (TDD) workflow tailored for Miyabi using the Red-Green-Refactor cycle with Rust-specific patterns and commands. It codifies test structure, command recipes, and CI integration so you can iterate reliably on features and fixes. Use it to enforce fast, deterministic tests and to keep code quality checks (clippy, fmt, coverage) as part of the flow.

How this skill works

The skill guides you through three phases: write a failing test (Red), implement the minimal code to pass tests (Green), then improve code while keeping tests green (Refactor). It provides Rust test patterns (Result, async, mocks, property tests, fixtures, snapshots), folder conventions, and concrete cargo commands for local and CI runs. Built-in checks include clippy, formatting, and coverage commands to keep PRs healthy.

When to use it

  • Implementing a new feature where behavior must be specified first
  • Fixing a bug and adding a regression test before changing code
  • Adding tests for an uncovered critical path or edge case
  • Refactoring code while preserving existing behavior
  • Setting up CI pipelines that must enforce tests, linting, and formatting

Best practices

  • Follow Red-Green-Refactor strictly: failing test, minimal pass, then cleanup
  • Use Arrange-Act-Assert and one-assert-per-test to keep tests clear and focused
  • Aim for fast unit tests (majority), keep integration and E2E to key paths
  • Name tests to express intent, not implementation details
  • Run cargo clippy and cargo fmt in CI and require passing coverage thresholds

Example use cases

  • Write a failing unit test for a new API handler, implement minimal handler, then refactor internals
  • Add regression tests for a reported bug, verify failure locally, fix code, and confirm tests pass in CI
  • Create async integration tests using tokio and mock external services to verify component interactions
  • Introduce property-based tests for serialization round-trips to catch edge cases
  • Add snapshot tests for output format changes before and after refactor

FAQ

What commands should I run locally to follow the flow?

Start with cargo test <test_name> (expect failure), implement minimal code, then run cargo test <test_name> again. After passing, run cargo clippy and cargo fmt -- --check, and optionally cargo llvm-cov for coverage.

How do I handle flaky tests?

Run with -- --test-threads=1 to disable parallelism, add logging (RUST_LOG=debug), and reproduce the failure in a loop to isolate nondeterminism before fixing.