Skip to content
Back to skills

Praxis Tdd

ASecurity

Use when implementing any feature or bugfix with strict baby-steps TDD - enforces minimal code and mandatory checkpoints

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 5, 2026
code-qualitytypescriptrustgotestingdebuggingrefactoringapi

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned October 5, 2026

npx -y skills add txreplay/praxis --skill praxis-tdd --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Praxis Tdd?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Praxis Tdd
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/txreplay-praxis-tdd/badge)](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.

Download with Pro
SKILL.md
---
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.

Files in this skill

  • SKILL.md10.7 KB
  • skill.json93 B
  • testing-anti-patterns.md8.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…