Use when implementing any feature or bugfix with strict baby-steps TDD - enforces minimal code and mandatory checkpoints
Pro scans all 3 files and shows the line behind each finding
Scanned 10/5/2026
npx -y skills add txreplay/praxis --skill praxis-tdd --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Praxis Tdd?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/txreplay-praxis-tdd)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: praxis-tdd
description: Use when implementing any feature or bugfix with strict baby-steps TDD - enforces minimal code and mandatory checkpoints
---
# Strict Test-Driven Development
## Philosophy
This is Kent Beck's original TDD discipline. Not "write tests first" - that's too loose. This is **baby steps with proof at every stage**.
**The Rule:** You cannot write a single line of implementation without a failing test that DEMANDS it.
**The Discipline:** The simplest code that passes is often absurdly simple. That's correct. Resist the urge to "just write the real thing."
## The Cycle
```
RED → verify → GREEN → verify → REFACTOR → verify → COMMIT → verify → repeat
↑ ↑ ↑ ↑
CHECKPOINT CHECKPOINT CHECKPOINT CHECKPOINT
(confirm) (confirm) (confirm) (confirm)
```
Every arrow with "CHECKPOINT" requires user confirmation before proceeding.
## Mandatory Checkpoints
### CHECKPOINT 1: Before Writing Test
State the behavior in ONE sentence:
> "I will write a test that [specific behavior]"
**Wait for confirmation.**
### CHECKPOINT 2: After RED (Test Fails)
Show:
1. The test code
2. The failure message
3. Confirm: "This fails because [the feature is missing], not because of [typo/syntax/setup error]"
**Wait for confirmation.**
### CHECKPOINT 3: Before GREEN (Propose Simplest Code)
State the absolute simplest code that would pass:
> "The simplest code to pass this test is: [code]"
This might be:
- `return true`
- `return 'hardcoded string'`
- `return null`
- `if (input === 1) return true`
**If it feels stupid, it's probably right.**
**Wait for confirmation.**
### CHECKPOINT 4: After GREEN (Test Passes)
Show:
1. The implementation
2. Test output (passing)
3. Ask: "Add another test to force generalization, or is this behavior complete?"
**Wait for confirmation.**
### CHECKPOINT 5: Before Refactor (Production AND Test Code)
Refactoring applies to **both production code AND test code**. You MUST review test files for quality — they are first-class code.
**Step 1: Run the Test Refactoring Checklist**
Go through each item. You cannot say "no refactoring needed" without explicitly evaluating every point:
| Check | What to look for |
|-------|-----------------|
| **Duplicated setup** | Is the same object/state created in multiple tests? Extract to a shared helper, `beforeEach`, or factory function. |
| **Duplicated act** | Are multiple tests calling the same function with the same boilerplate around it? Extract a `subject()` or `act()` helper. |
| **Unclear test names** | Does each test name read as a behavior specification? Rename vague names like "works correctly" to "returns false for odd numbers". |
| **Arrange/Act/Assert clarity** | Is each test clearly structured in 3 sections? Add blank lines to separate them if needed. |
| **Magic values** | Are there unexplained literals? Extract to named constants or use descriptive variable names. |
| **Long test bodies** | Is any test longer than ~10 lines? Consider extracting setup into helpers. |
| **Inconsistent patterns** | Do all tests in the file follow the same structure and conventions? |
**Step 2: State your findings**
> "Test file review:
> - [item]: [finding — either a specific refactoring or why it's genuinely clean]
> - ...
> Production code review:
> - [specific change or why it's clean]"
You must list **at least 3 checklist items** you evaluated with concrete findings. A bare "no refactoring needed" without this evaluation is FORBIDDEN.
**Wait for confirmation.**
### CHECKPOINT 6: After Cycle (Commit)
Propose a commit for the completed cycle:
1. Summarize the behavior implemented
2. Suggest commit message: `test(scope): [behavior implemented]`
Example:
> "Cycle complete. Ready to commit: `test(isEven): return true for even numbers`"
**Wait for confirmation, then create the commit.**
## Hard Constraints
### Code Justification Rule
No arbitrary line limits. Instead:
**Every line of implementation must trace to a test assertion.**
Before writing code, you must answer for each line:
> "This line exists because test X asserts Y"
If you cannot justify a line against a current test, delete it.
### No Speculation
These are FORBIDDEN until a test demands them:
- Parameters with default values
- Configuration options
- Error handling for hypothetical cases
- Generic types
- Helper functions
- Comments explaining "why"
### The Triangulation Rule
**You cannot generalize without two failing tests.**
| Tests | Allowed Implementation |
|-------|----------------------|
| `add(1,1) → 2` | `return 2` |
| `add(1,1) → 2` AND `add(2,3) → 5` | `return a + b` |
One test = hardcode. Two tests = generalize. No exceptions.
## Example: Building `isEven`
### Cycle 1
**CHECKPOINT 1:** "I will write a test that checks if 2 is even"
```typescript
test('returns true for 2', () => {
expect(isEven(2)).toBe(true)
})
```
**CHECKPOINT 2:** "Fails because isEven doesn't exist"
**CHECKPOINT 3:** "Simplest code: `return true`"
```typescript
function isEven(n: number): boolean {
return true
}
```
**CHECKPOINT 4:** "Passes. Add another test?"
**CHECKPOINT 5:**
> "Test file review:
> - Duplicated setup: only one test, nothing to deduplicate yet.
> - Test name: 'returns true for 2' — clear behavior specification.
> - Arrange/Act/Assert: single-line test, structure is clear.
>
> Production code review: single return statement, nothing to clean up."
**CHECKPOINT 6:** "Cycle complete. Ready to commit: `test(isEven): return true for 2`"
### Cycle 2
**CHECKPOINT 1:** "I will write a test that checks if 3 is not even"
```typescript
test('returns false for 3', () => {
expect(isEven(3)).toBe(false)
})
```
**CHECKPOINT 2:** "Fails because we return true for everything"
**CHECKPOINT 3:** "Simplest code: `return n === 2`"
Wait - that still passes both tests. Even simpler.
Actually: `return n % 2 === 0` is needed to pass both. But is there something simpler?
`return n !== 3` - passes both tests!
**This is the discipline.** Present the truly simplest, let user decide.
```typescript
function isEven(n: number): boolean {
return n !== 3
}
```
**CHECKPOINT 4:** "Passes. This is absurd but correct. Add another test?"
**CHECKPOINT 5:**
> "Test file review:
> - Duplicated setup: both tests call `isEven(n)` directly with no shared setup needed — clean.
> - Test names: 'returns true for 2' and 'returns false for 3' — both describe behavior clearly.
> - Consistency: both tests follow the same `expect(isEven(x)).toBe(y)` pattern — consistent.
>
> Production code review: single return statement, nothing to clean up."
**CHECKPOINT 6:** "Cycle complete. Ready to commit: `test(isEven): return false for 3`"
### Cycle 3
**CHECKPOINT 1:** "I will write a test that checks if 5 is not even"
```typescript
test('returns false for 5', () => {
expect(isEven(5)).toBe(false)
})
```
**CHECKPOINT 2:** "Fails because `5 !== 3` is true, but we need false"
**CHECKPOINT 3:** "Now I need real logic. Simplest: `return n % 2 === 0`"
```typescript
function isEven(n: number): boolean {
return n % 2 === 0
}
```
**CHECKPOINT 4:** "All tests pass. Generalization earned."
**CHECKPOINT 5:**
> "Test file review:
> - Duplicated setup: three tests all follow `expect(isEven(x)).toBe(y)`. This is fine — no shared state to extract. Each test is a single assertion with no setup overhead.
> - Test names: 'returns true for 2', 'returns false for 3', 'returns false for 5' — all describe input/output behavior. However, now that we have the generalized implementation, consider grouping with `describe` blocks for even/odd cases if more tests are added.
> - Magic values: 2, 3, 5 are self-explanatory for an isEven function — no extraction needed.
>
> Production code review: `n % 2 === 0` is the final generalized form — clean and minimal."
**CHECKPOINT 6:** "Cycle complete. Ready to commit: `test(isEven): correctly identify even and odd numbers`"
## Anti-Patterns
| You Think | Reality | Action |
|-----------|---------|--------|
| "I'll just write the real implementation" | You haven't earned it | Write simpler code |
| "This obviously needs X" | Nothing is obvious until tested | Wait for failing test |
| "Let me handle this edge case" | Edge case needs its own test first | Stop, write test |
| "This is too slow" | Slow is fast. Debugging is slower. | Trust the process |
| "The user will think I'm stupid" | The user asked for this discipline | Continue |
| "One more line won't hurt" | It will. That's how debt starts. | Remove the line |
## When to Use This Skill vs Regular TDD
| Situation | Use |
|-----------|-----|
| Learning TDD discipline | **Strict TDD** |
| Training yourself to slow down | **Strict TDD** |
| Complex logic where you keep over-engineering | **Strict TDD** |
| Simple CRUD with clear patterns | Regular TDD |
| Time pressure with well-understood domain | Regular TDD |
## Red Flags - STOP Immediately
If I do any of these, call me out:
- Skip a checkpoint
- Generalize without two tests
- Add "just one more thing"
- Say "while I'm here..."
- Propose real implementation when fake would pass
- Handle errors without a test requiring it
- Write a line I cannot justify against a test assertion
## Verification Checklist
Before marking work complete:
- [ ] Every new function/method has a test
- [ ] Watched each test fail before implementing
- [ ] Each test failed for expected reason (feature missing, not typo)
- [ ] Wrote minimal code to pass each test
- [ ] All tests pass
- [ ] Output pristine (no errors, warnings)
- [ ] Tests use real code (mocks only if unavoidable)
- [ ] Edge cases and errors covered
Can't check all boxes? You skipped TDD. Start over.
## When Stuck
| Problem | Solution |
|---------|----------|
| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
| Test too complicated | Design too complicated. Simplify interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify design. |
## Debugging Integration
Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
Never fix bugs without a test.
## Testing Anti-Patterns
When adding mocks or test utilities, read @testing-anti-patterns.md to avoid common pitfalls:
- Testing mock behavior instead of real behavior
- Adding test-only methods to production classes
- Mocking without understanding dependencies
## Final Note
This process feels slow. It is slow. That's the point.
The goal is not speed. The goal is:
1. **Proof** that every line of code is necessary
2. **Discipline** to resist speculation
3. **Trust** in the test suite
Speed comes later, when the discipline is internalized.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!