
tdd-workflow
by ShunsukeHayashi
ð€ First open-source, economically-governed, beginner-friendly autonomous development framework built on Issue-Driven Development | è¶ åå¿è ã§ã䜿ããèªåŸåéçºãã¬ãŒã ã¯ãŒã¯
SKILL.md
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
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 (倱æãããã¹ããæžã)
ç®ç: å®è£ ãããæ©èœã®ä»æ§ããã¹ããšããŠææå
#[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());
}
}
å®è¡:
cargo test test_new_feature --no-run && cargo test test_new_feature
# æåŸ
: FAILED (ãã¹ãã倱æããããšã確èª)
Phase 2: GREEN (ãã¹ããéãæå°å®è£ )
ç®ç: ãã¹ããéãããã®æå°éã®ã³ãŒãå®è£
pub fn new_feature(input: &str) -> Result<String, Error> {
// æå°éã®å®è£
if input.is_empty() {
return Err(Error::InvalidInput);
}
Ok(expected_output.to_string())
}
å®è¡:
cargo test test_new_feature
# æåŸ
: PASSED (å
šãã¹ãæå)
Phase 3: REFACTOR (ã³ãŒãæ¹å)
ç®ç: ãã¹ããç¶æããªããã³ãŒãåè³ªãæ¹å
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(())
}
å®è¡:
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åã®ãã¹ã
#[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颿°ã®ãã¹ã
#[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: ã¢ãã¯äœ¿çš
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: ããããã£ããŒã¹ãã¹ã
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: ãã¹ããã£ã¯ã¹ãã£
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: ã¹ãããã·ã§ãããã¹ã
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);
}
ã³ãã³ããªãã¡ã¬ã³ã¹
åºæ¬ãã¹ãã³ãã³ã
# å
šãã¹ãå®è¡
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
é«åºŠãªãã¹ãã³ãã³ã
# ããã¥ã¡ã³ããã¹ã
cargo test --doc
# çµ±åãã¹ãã®ã¿
cargo test --test integration_test
# ãã³ãããŒã¯
cargo bench
# ã«ãã¬ããž (cargo-llvm-cov)
cargo llvm-cov --workspace --html
# ããããã£ãã¹ã(é·æé)
PROPTEST_CASES=10000 cargo test
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
ãã¹ãæ§é
ãã£ã¬ã¯ããªæ§æ
crates/miyabi-xxx/
âââ src/
â âââ lib.rs
â âââ feature.rs # æ©èœã³ãŒã
âââ tests/
â âââ integration_test.rs # çµ±åãã¹ã
â âââ e2e_test.rs # E2Eãã¹ã
âââ benches/
âââ benchmark.rs # ãã³ãããŒã¯
ãã¹ãã¢ãžã¥ãŒã«æ§æ
// 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ã¢ãµãŒã·ã§ã³åå
#[test] fn test_single_assertion() { let result = operation(); assert_eq!(result, expected); } -
AAA ãã¿ãŒã³ (Arrange-Act-Assert)
#[test] fn test_aaa_pattern() { // Arrange let input = setup_input(); // Act let result = operation(input); // Assert assert!(result.is_ok()); } -
æå³ã衚ããã¹ãå
#[test] fn when_input_is_empty_returns_validation_error() { } #[test] fn given_valid_user_when_login_then_returns_token() { } -
ãã¹ãããŒã¿ã®æç€ºå
#[test] fn test_with_explicit_data() { let user = User { id: 1, name: "Test User".to_string(), email: "test@example.com".to_string(), }; // ... }
DON'T (éæšå¥š)
-
ãã¹ãéã®äŸå
// BAD: ãã¹ãã®å®è¡é åºã«äŸå static mut SHARED_STATE: i32 = 0; -
å®è£ 詳现ã®ãã¹ã
// BAD: å éšæ§é ã«äŸå assert_eq!(result.internal_cache.len(), 5); -
éæ±ºå®çãã¹ã
// BAD: æéäŸå assert!(Instant::now() > start_time); -
é床ã«è€éãªã»ããã¢ãã
// BAD: 50è¡ã®ã»ããã¢ããã³ãŒã
ã«ãã¬ããžã¬ã€ãã©ã€ã³
ã«ãã¬ããžç®æš
| ã³ã³ããŒãã³ã | ç®æš | åªå 床 |
|---|---|---|
| Core Types | 90%+ | P0 |
| Business Logic | 85%+ | P0 |
| API Handlers | 80%+ | P1 |
| Utilities | 70%+ | P2 |
| CLI | 60%+ | P2 |
ã«ãã¬ããžæž¬å®
# ã€ã³ã¹ããŒã«
cargo install cargo-llvm-cov
# 枬å®å®è¡
cargo llvm-cov --workspace --html
# ã¬ããŒã確èª
open target/llvm-cov/html/index.html
ã«ãã¬ããžé€å€
// ã«ãã¬ããžããé€å€
#[cfg(not(tarpaulin_include))]
fn debug_only_function() { }
// ãŸãã¯
#[coverage(off)]
fn uncoverable_function() { }
CI/CDçµ±å
GitHub Actionsèšå®
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
ãã©ãã«ã·ã¥ãŒãã£ã³ã°
ãã¹ããäžå®å®
çç¶: åããã¹ããæã 倱æãã
察åŠ:
# 䞊åå®è¡ãç¡å¹å
cargo test -- --test-threads=1
# 詳现ãã°
RUST_LOG=debug cargo test
# ç¹å®ãã¹ãã100åå®è¡
for i in {1..100}; do cargo test flaky_test || exit 1; done
ãã¹ããé ã
çç¶: ãã¹ãå®è¡ã«æéãããã
察åŠ:
# ã³ã³ãã€ã«æéã®ç¢ºèª
cargo build --timings
# ãã¹ãæéã®èšæž¬
cargo test -- -Z unstable-options --report-time
# 䞊å床調æŽ
cargo test -- --test-threads=8
ã¢ãã¯ãåäœããªã
çç¶: ã¢ãã¯ãåŒã³åºãããªã
察åŠ:
// 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: ã»ãã¥ãªãã£ãã¹ã
ã¹ã³ã¢
ç·åã¹ã³ã¢
ãªããžããªã®åè³ªææšã«åºã¥ãè©äŸ¡
SKILL.mdãã¡ã€ã«ãå«ãŸããŠãã
ã©ã€ã»ã³ã¹ãèšå®ãããŠãã
100æå以äžã®èª¬æããã
GitHub Stars 100以äž
3ã¶æä»¥å ã«æŽæ°
10å以äžãã©ãŒã¯ãããŠãã
ãªãŒãã³Issueã50æªæº
ããã°ã©ãã³ã°èšèªãèšå®ãããŠãã
1ã€ä»¥äžã®ã¿ã°ãèšå®ãããŠãã
ã¬ãã¥ãŒ
ã¬ãã¥ãŒæ©èœã¯è¿æ¥å ¬éäºå®ã§ã

