From fenix
Test-Driven Development discipline for AI agents. Red-Green-Refactor cycle, detection heuristic, and anti-patterns. Use when implementing features or fixing bugs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/fenix:fenix-tdd-workflowThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill defines the Test-Driven Development discipline for implementing code with Fenix workflows. TDD is not about writing tests — it's about **designing code through tests**.
This skill defines the Test-Driven Development discipline for implementing code with Fenix workflows. TDD is not about writing tests — it's about designing code through tests.
Before implementing anything, ask yourself:
Can I write
expect(fn(input)).toBe(output)before writingfn?
<EXTREMELY_IMPORTANT> Even when TDD doesn't apply, you MUST still write tests. The only question is whether the test comes before or after the implementation. "No tests needed" is never an acceptable answer. </EXTREMELY_IMPORTANT>
Write a test that describes the behavior you want:
// Good: tests WHAT, not HOW
test("should return 404 when user not found", async () => {
const response = await request(app).get("/users/nonexistent");
expect(response.status).toBe(404);
expect(response.body.error).toBe("User not found");
});
# Good: tests behavior, not implementation
def test_calculate_discount_for_premium_user():
user = User(tier="premium")
order = Order(total=100.00)
assert calculate_discount(user, order) == 15.00
Run the test. It MUST fail. If it passes:
Write the minimum code to make the test pass:
Rules for the Green phase: 1. Do NOT add features the test doesn't require 2. Do NOT optimize prematurely 3. Do NOT handle edge cases not covered by tests — write more tests first 4. Do NOT refactor during Green — that's the next phase 5. It's OK if the code is ugly — Green is about correctness, not beautyRun the test. It MUST pass.
With green tests as your safety net:
Run tests after EVERY change. If a test fails, you broke something — undo and try again.
Write the next test for the next behavior. Continue the cycle until all acceptance criteria are met.
// Bad: tests implementation details
test("should call userRepository.findById", () => { ... });
// Good: tests observable behavior
test("should return user profile for valid ID", () => { ... });
# Bad: testing multiple unrelated things
def test_user_creation():
user = create_user("alice", "[email protected]")
assert user.name == "alice"
assert user.email == "[email protected]"
assert user.created_at is not None
assert send_welcome_email.called # different concern!
# Good: separate tests for separate concepts
def test_user_created_with_correct_fields():
user = create_user("alice", "[email protected]")
assert user.name == "alice"
assert user.email == "[email protected]"
def test_welcome_email_sent_on_creation():
create_user("alice", "[email protected]")
assert send_welcome_email.called
After the happy path passes, add tests for:
The test name should read like a specification:
should return empty list when no users match filtershould throw validation error when email is invalidshould retry request up to 3 times on timeoutNever suppress type errors. No @ts-ignore, no # type: ignore, no as any. Type errors are bugs caught early. Fix them.
Never commit with failing tests. All tests must pass before a task is marked as done. No exceptions.
Never mock what you don't own. Mock your adapters, not third-party libraries directly. If the library changes, your mock won't catch it.
Tests must be deterministic. No flaky tests. No tests that depend on time, network, or random values without controlling them.
test("should apply discount to order total", () => {
// Arrange
const order = createOrder({ total: 100, coupon: "SAVE10" });
// Act
const result = applyDiscount(order);
// Assert
expect(result.total).toBe(90);
});
Use the simplest double that works:
| Double | When to use |
|---|---|
| Stub | Return a fixed value. Use for dependencies whose output you control. |
| Spy | Verify a call was made. Use when the side effect matters. |
| Mock | Set expectations upfront. Use sparingly — prefer stubs + assertions. |
| Fake | Lightweight implementation (e.g., in-memory DB). Use for integration tests. |
Test at the edges of your system, not in the middle:
[External Input] → [Your Boundary] → [Your Logic] → [Your Boundary] → [External Output]
↑ ↑ ↑
Validate here Test here Test here
| Feeling | Likely cause | Fix |
|---|---|---|
| "I can't write a test first" | You don't understand the requirements yet | Go back to the task description. Clarify with the user. |
| "The test is too complex" | The function does too much | Break the function into smaller pieces. Test each piece. |
| "I need to test private methods" | The public interface is too coarse | Refactor to expose behavior through the public API. |
| "Tests are slow" | Too many integration tests, not enough unit tests | Apply the test pyramid: many unit, some integration, few E2E. |
| "Tests break when I refactor" | Tests are coupled to implementation | Rewrite tests to assert on behavior, not implementation details. |
npx claudepluginhub fenix-assistant/fenix-plugin --plugin fenixGuides TDD workflow with red-green-refactor cycle: plan interfaces, tracer bullet tests, minimal implementation to green, refactor under tests. For explicit TDD requests only.
Guides the RED-GREEN-REFACTOR cycle for test-driven development, including AAA pattern, test prioritization, and anti-patterns. Useful when writing tests before code or implementing new features.