بنقرة واحدة
test-driven-development
// Enforce Red-Green-Refactor TDD workflow for writing tests before implementation
// Enforce Red-Green-Refactor TDD workflow for writing tests before implementation
| name | test-driven-development |
| version | 1.0.0 |
| description | Enforce Red-Green-Refactor TDD workflow for writing tests before implementation |
A disciplined approach to development where tests drive the design and implementation of code.
Use this skill when:
┌─────────┐
│ RED │ ──► Write failing test
└────┬────┘
│
▼
┌─────────┐
│ GREEN │ ──► Write minimal code to pass
└────┬────┘
│
▼
┌──────────┐
│ REFACTOR │ ──► Improve code quality
└─────┬────┘
│
└──► Repeat
Goal: Write a test that fails because the feature doesn't exist yet
Understand the requirement
Write a minimal test
Run the test
#[test]
fn test_<behavior_description>() {
// Arrange - Set up test data
let input = /* test input */;
let expected = /* expected output */;
// Act - Execute the behavior
let result = function_under_test(input);
// Assert - Verify the outcome
assert_eq!(result, expected);
}
// ✅ Good: Test behavior with descriptive name
#[test]
fn test_calculate_total_returns_sum_of_item_prices() {
let items = vec![
Item { name: "A", price: 10.0 },
Item { name: "B", price: 20.0 },
];
let result = calculate_total(&items);
assert_eq!(result, 30.0);
}
// ✅ Good: Test edge case
#[test]
fn test_calculate_total_returns_zero_for_empty_items() {
let items = vec![];
let result = calculate_total(&items);
assert_eq!(result, 0.0);
}
// ✅ Good: Test error case
#[test]
fn test_parse_config_returns_error_for_invalid_toml() {
let invalid_toml = "not valid toml [[[";
let result = parse_config(invalid_toml);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ConfigError::InvalidToml(_)));
}
// ❌ Bad: Testing implementation details
#[test]
fn test_internal_parser_helper() {
// Don't test private functions directly
}
// ❌ Bad: Multiple assertions in one test
#[test]
fn test_everything() {
assert_eq!(func1(), 1);
assert_eq!(func2(), 2);
assert_eq!(func3(), 3); // If func2 fails, we don't test func3
}
// Pattern: test_<unit>_<scenario>_<expected_result>
#[test]
fn test_parse_date_with_valid_iso_format_returns_date() { }
#[test]
fn test_parse_date_with_invalid_format_returns_error() { }
#[test]
fn test_parse_date_with_empty_string_returns_error() { }
#[test]
fn test_parse_date_with_future_date_returns_date() { }
Goal: Write the minimal code to make the test pass
Write minimal implementation
Run the test
Keep it simple
// Test
#[test]
fn test_add_returns_sum_of_two_numbers() {
assert_eq!(add(2, 3), 5);
}
// Green (minimal implementation)
fn add(a: i32, b: i32) -> i32 {
5 // Hardcoded! That's OK for now
}
// Later, after more tests...
#[test]
fn test_add_with_negative_numbers() {
assert_eq!(add(-1, 1), 0);
}
// Now we need proper implementation
fn add(a: i32, b: i32) -> i32 {
a + b // Generalized solution
}
// Start with the simplest thing that could possibly work
pub fn format_name(first: &str, last: &str) -> String {
format!("{} {}", first, last) // Simple concatenation
}
// As tests reveal edge cases, refine
#[test]
fn test_format_name_handles_whitespace() {
assert_eq!(format_name(" John ", " Doe "), "John Doe");
}
// Refined implementation
pub fn format_name(first: &str, last: &str) -> String {
format!("{} {}", first.trim(), last.trim())
}
Goal: Improve code quality while keeping tests green
Identify improvements
Make one change at a time
Verify tests still pass
// Before refactor: Duplicate logic
fn process_user(user: &User) -> String {
let name = format!("{} {}", user.first_name.trim(), user.last_name.trim());
name
}
fn process_admin(admin: &Admin) -> String {
let name = format!("{} {}", admin.first_name.trim(), admin.last_name.trim());
name
}
// After refactor: Extract common logic
fn format_name(first: &str, last: &str) -> String {
format!("{} {}", first.trim(), last.trim())
}
fn process_user(user: &User) -> String {
format_name(&user.first_name, &user.last_name)
}
fn process_admin(admin: &Admin) -> String {
format_name(&admin.first_name, &admin.last_name)
}
REFACTORING:
- [ ] Code is readable
- [ ] Names are descriptive
- [ ] Functions are focused
- [ ] No duplication
- [ ] Proper abstraction
- [ ] Tests still pass
1. Write acceptance criteria
2. Break down into small units
3. For each unit:
a. RED: Write failing test
b. GREEN: Make it pass
c. REFACTOR: Clean up
4. Integration test
5. Manual verification
1. Write test that reproduces bug
- Test should fail (demonstrating bug exists)
2. Fix the code
- Test should now pass
3. Refactor if needed
4. Verify no regression
// Step 1: Write test that demonstrates bug
#[test]
fn test_parse_json_handles_empty_array() {
let json = "[]";
let result = parse_json(json);
// This fails because current implementation panics on empty array
assert_eq!(result.unwrap(), vec![]);
}
// Step 2: Run test - it fails (RED)
// thread 'test_parse_json_handles_empty_array' panicked at 'index out of bounds'
// Step 3: Fix the code (GREEN)
fn parse_json(json: &str) -> Result<Vec<Item>> {
let items: Vec<Item> = serde_json::from_str(json)?;
Ok(items) // Now handles empty array correctly
}
// Step 4: Run test - it passes (GREEN)
// Step 5: Refactor if needed (REFACTOR)
// Code is clean, no refactor needed
// Step 6: Run all tests
cargo test // All tests pass
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_unit_of_work() {
// Fast, isolated, focused
let result = parse_input("test");
assert!(result.is_ok());
}
}
// tests/integration_test.rs
use ktme::*;
use tempfile::TempDir;
#[test]
fn test_full_workflow() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
// Initialize
let db = Database::new(&db_path).unwrap();
// Create
let service = db.create_service("test-service").unwrap();
// Read
let retrieved = db.get_service("test-service").unwrap();
assert_eq!(service.name, retrieved.unwrap().name);
// Update
db.update_service_description("test-service", "Updated").unwrap();
// Delete
db.delete_service("test-service").unwrap();
}
use proptest::prelude::*;
proptest! {
#[test]
fn test_parse_format_roundtrip(s in "\\PC*") {
// For any string, parsing and formatting should be reversible
let parsed = parse_input(&s);
if let Ok(value) = parsed {
let formatted = format_output(&value);
let reparsed = parse_input(&formatted).unwrap();
assert_eq!(value, reparsed);
}
}
}
src/
├── lib.rs
├── parser.rs
│ └── #[cfg(test)] mod tests { }
└── database.rs
└── #[cfg(test)] mod tests { }
tests/
├── integration_test.rs
├── common/
│ └── mod.rs
└── fixtures/
└── test_data.json
// src/parser.rs
pub fn parse_input(input: &str) -> Result<ParsedData> {
// Implementation
}
#[cfg(test)]
mod tests {
use super::*;
mod parse_input {
use super::*;
#[test]
fn with_valid_input_returns_parsed_data() { }
#[test]
fn with_empty_input_returns_error() { }
#[test]
fn with_invalid_format_returns_error() { }
}
mod edge_cases {
use super::*;
#[test]
fn handles_unicode_correctly() { }
#[test]
fn handles_very_long_input() { }
}
}
// ❌ Bad: Tests depend on each other
static mut COUNTER: i32 = 0;
#[test]
fn test_increment() {
unsafe { COUNTER += 1; }
assert_eq!(unsafe { COUNTER }, 1);
}
#[test]
fn test_increment_again() {
unsafe { COUNTER += 1; }
assert_eq!(unsafe { COUNTER }, 2); // Depends on test order!
}
// ✅ Good: Each test is independent
#[test]
fn test_increment() {
let mut counter = 0;
counter += 1;
assert_eq!(counter, 1);
}
#[test]
fn test_increment_again() {
let mut counter = 0;
counter += 1;
assert_eq!(counter, 1); // Independent of other tests
}
// ❌ Bad: Unclear test
#[test]
fn test1() {
let x = func("a", "b");
assert!(x);
}
// ✅ Good: Clear test
#[test]
fn test_validate_credentials_returns_true_for_valid_user() {
let username = "valid_user";
let password = "valid_pass123";
let is_valid = validate_credentials(username, password);
assert!(is_valid);
}
#[test]
fn test_user_can_be_created_with_valid_data() {
// Arrange - Set up test data
let name = "John Doe";
let email = "john@example.com";
// Act - Perform the action
let user = User::new(name, email);
// Assert - Verify the result
assert_eq!(user.name, name);
assert_eq!(user.email, email);
}
# Run all tests
cargo test
# Run specific test
cargo test test_parse_input
# Run tests in specific module
cargo test parser::tests
# Run tests matching pattern
cargo test "test_parse"
# Show println! output
cargo test -- --nocapture
# Run single test by name
cargo test test_specific_function --exact
# Run tests in parallel
cargo test -- --test-threads=4
# Run ignored tests
cargo test -- --ignored
❌ Don't:
✅ Do:
This skill integrates with Kilo's workflow:
Use with other skills:
Comprehensive code review framework for evaluating code quality, security, performance, and maintainability
Standards and workflows for creating and maintaining high-quality documentation
Manage Git worktrees for parallel development workflows without switching branches
Core Kilo agent configuration with comprehensive coding capabilities and workflow management
A systematic 4-phase debugging framework for reproducing, analyzing, fixing, and verifying bugs
Template for creating new skills - customize and rename for your use case