diff --git a/.gitignore b/.gitignore index d624eddd..96934e54 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules/ .claude/.simplify-ignore-cache/ .claude/sdd-cache/ evals/results/ +__pycache__/ diff --git a/README.md b/README.md index 38c4b1e3..3700fbaa 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ Quick-reference material that skills pull in when needed: | Reference | Covers | |-----------|--------| | [definition-of-done.md](references/definition-of-done.md) | Project-wide standing bar every change clears, contrasted with per-task acceptance criteria | -| [testing-patterns.md](references/testing-patterns.md) | Test structure, naming, mocking, React/API/E2E examples, anti-patterns | +| [testing-patterns.md](references/testing-patterns.md) | Test structure, naming, mocking, React/API/E2E examples, anti-patterns (JavaScript/TypeScript) | | [security-checklist.md](references/security-checklist.md) | Pre-commit checks, auth, input validation, headers, CORS, OWASP Top 10 | | [performance-checklist.md](references/performance-checklist.md) | Core Web Vitals targets, frontend/backend checklists, measurement commands | | [accessibility-checklist.md](references/accessibility-checklist.md) | Keyboard nav, screen readers, visual design, ARIA, testing tools | diff --git a/evals/cases/test-driven-development.json b/evals/cases/test-driven-development.json index 3ecb7903..7b2e7e6a 100644 --- a/evals/cases/test-driven-development.json +++ b/evals/cases/test-driven-development.json @@ -52,6 +52,21 @@ "A regression test covers the reported three-at-3.335 currency case", "The full suite is run after the minimal fix" ] + }, + { + "id": 3, + "prompt": "Add debit entries to the ledger, test-first. A debit subtracts its amount from the balance and must not drive the balance below zero.", + "expected_output": "A failing unittest written and shown failing first, a minimal implementation, and the full suite run with the repository's own Python test command", + "files": [ + "test-driven-development-ecosystem" + ], + "expectations": [ + "The repository's stack (Python, unittest) is identified before any test command is chosen", + "Tests are run with the repository's own command (python3 -m unittest), not npm test or another ecosystem's tool", + "A failing test is written and shown failing before the implementation", + "The below-zero rule gets its own test case", + "The full suite is run after the change" + ] } ] } diff --git a/evals/fixtures/test-driven-development-ecosystem/README.md b/evals/fixtures/test-driven-development-ecosystem/README.md new file mode 100644 index 00000000..ce71454c --- /dev/null +++ b/evals/fixtures/test-driven-development-ecosystem/README.md @@ -0,0 +1,9 @@ +# Ledger + +Small Python ledger utilities. No dependencies beyond the standard library. + +Run the tests: + +``` +python3 -m unittest +``` diff --git a/evals/fixtures/test-driven-development-ecosystem/ledger.py b/evals/fixtures/test-driven-development-ecosystem/ledger.py new file mode 100644 index 00000000..0b8c09b0 --- /dev/null +++ b/evals/fixtures/test-driven-development-ecosystem/ledger.py @@ -0,0 +1,15 @@ +"""Simple in-memory ledger utilities.""" + + +def apply_entries(balance, entries): + """Apply entries to a starting balance and return the result. + + Entries are (kind, amount) tuples. Only "credit" entries are + supported; anything else raises ValueError. + """ + for kind, amount in entries: + if kind == "credit": + balance += amount + else: + raise ValueError(f"unsupported entry kind: {kind}") + return balance diff --git a/evals/fixtures/test-driven-development-ecosystem/test_ledger.py b/evals/fixtures/test-driven-development-ecosystem/test_ledger.py new file mode 100644 index 00000000..617b0a01 --- /dev/null +++ b/evals/fixtures/test-driven-development-ecosystem/test_ledger.py @@ -0,0 +1,19 @@ +import unittest + +from ledger import apply_entries + + +class ApplyEntriesTest(unittest.TestCase): + def test_applies_a_single_credit(self): + self.assertEqual(apply_entries(100, [("credit", 25)]), 125) + + def test_applies_multiple_credits_in_order(self): + self.assertEqual(apply_entries(0, [("credit", 10), ("credit", 5)]), 15) + + def test_rejects_unknown_entry_kinds(self): + with self.assertRaises(ValueError): + apply_entries(0, [("transfer", 10)]) + + +if __name__ == "__main__": + unittest.main() diff --git a/references/testing-patterns.md b/references/testing-patterns.md index b97de3aa..bc96e271 100644 --- a/references/testing-patterns.md +++ b/references/testing-patterns.md @@ -1,6 +1,6 @@ -# Testing Patterns Reference +# Testing Patterns Reference (JavaScript/TypeScript) -Quick reference for common testing patterns across the stack. Use alongside the `test-driven-development` skill. +Quick reference of JavaScript/TypeScript testing patterns — Jest, React Testing Library, Supertest, and Playwright — illustrating the universal principles from the `test-driven-development` skill. The principles (Arrange-Act-Assert, naming, mock discipline, anti-patterns) apply in any ecosystem; the syntax and tooling shown here are JS/TS-specific. In another stack, follow the same principles with the repository's own test framework and commands. ## Table of Contents diff --git a/skills/test-driven-development/SKILL.md b/skills/test-driven-development/SKILL.md index c96a67f4..a0c9944d 100644 --- a/skills/test-driven-development/SKILL.md +++ b/skills/test-driven-development/SKILL.md @@ -21,6 +21,20 @@ Write a failing test before writing the code that makes it pass. For bug fixes, **Related:** For browser-based changes, combine TDD with runtime verification using Chrome DevTools MCP — see the Browser Testing section below. +## Discover the Stack First + +The TDD cycle is universal; the commands are not. Before writing the first test, discover how *this* repository tests, and use its commands for every RED, GREEN, and verification step: + +- **Language and build system** — `package.json`, `pom.xml`/`build.gradle`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`, a `Makefile` +- **Checked-in wrappers** — prefer `./gradlew`, `./mvnw`, `make test`, or a repo script over globally installed tools +- **Test framework and configuration** — and how it runs a single focused test vs the full suite +- **Existing conventions** — where tests live, how files are named, what patterns neighboring tests follow +- **Documented commands** — README, CONTRIBUTING, and CI workflows show the commands that actually gate merges + +Run the repository's focused-test command during the loop and its full-suite command before completion. Never assume a default like `npm test` — a Gradle, Cargo, or pytest project has its own equivalent. + +The examples below use TypeScript for illustration; the workflow is identical in any language once you've discovered the project's own tooling. + ## The TDD Cycle ``` @@ -344,7 +358,7 @@ This separation ensures the test is written without knowledge of the fix, making ## See Also -For detailed testing patterns, examples, and anti-patterns across frameworks, see `references/testing-patterns.md`. +For JavaScript/TypeScript testing patterns illustrating these principles — Jest, React Testing Library, Supertest, Playwright — see `references/testing-patterns.md`. The principles transfer to any ecosystem; the syntax and tools there are JS/TS-specific. ## Common Rationalizations @@ -361,6 +375,7 @@ For detailed testing patterns, examples, and anti-patterns across frameworks, se ## Red Flags - Writing code without any corresponding tests +- Reaching for a default test command (`npm test`) without checking what this repository actually uses - Tests that pass on the first run (they may not be testing what you think) - "All tests pass" but no tests were actually run - Bug fixes without reproduction tests @@ -374,7 +389,7 @@ For detailed testing patterns, examples, and anti-patterns across frameworks, se After completing any implementation: - [ ] Every new behavior has a corresponding test -- [ ] All tests pass: `npm test` +- [ ] The full suite passes, run with the repository's own test command (`npm test`, `./gradlew test`, `pytest`, `go test ./...`, ...) - [ ] Bug fixes include a reproduction test that failed before the fix - [ ] Test names describe the behavior being verified - [ ] No tests were skipped or disabled