Test-First Development
When writing code — whether new features, bug fixes, or refactors — follow this sequence strictly. When communicating with the user about tests or asking questions, lead with recommendations and explain your reasoning in plain language.
Process
- •
Understand what you are testing. Read existing test files to learn the project's testing conventions, framework, and patterns. Do not guess. If no tests exist yet, identify the test framework from the project's dependencies and set up the test infrastructure first.
- •
Write the tests first. Define the expected behavior through tests before writing any implementation. Cover:
- •The primary success path.
- •Edge cases relevant to the feature.
- •Error conditions and how they should be handled.
- •
Run the tests. They should fail. If they pass before you have written implementation code, your tests are not meaningful — they are testing nothing or testing the wrong thing. Rewrite them.
- •
Write the minimum implementation to make the tests pass. Do not add functionality beyond what the tests require.
- •
Run the tests again. All tests must pass. If any fail, fix the implementation. Do not modify the tests to match incorrect behavior.
- •
Run the full test suite. Ensure your changes have not broken existing functionality. If existing tests fail, investigate and fix the regression.
Detecting the Test Environment
Before writing tests, always check:
- •What test framework is installed (read the dependency manifest).
- •Where tests live in this project (check for
test/,tests/,__tests__/,*_test.go, etc.). - •How tests are run (check
package.jsonscripts,Makefile,pyproject.toml, etc.). - •What patterns existing tests follow (read at least one existing test file if available).
Do not assume Jest, pytest, or any specific framework. Read the project configuration.
Examples
<example title="Good test: tests behavior, not implementation"> // Testing a function that calculates shipping cost test("free shipping for orders over $50", () => { const cost = calculateShipping({ subtotal: 75.00, weight: 2.5 }); expect(cost).toBe(0); });test("flat rate shipping for orders under $50", () => { const cost = calculateShipping({ subtotal: 30.00, weight: 2.5 }); expect(cost).toBe(5.99); });
test("throws on negative subtotal", () => { expect(() => calculateShipping({ subtotal: -10, weight: 1 })) .toThrow("Subtotal must be positive"); }); </example>
<example title="Bad test: tests implementation details, brittle"> // This test is coupled to internal implementation — it will break // if you refactor the internals even if the behavior stays correct test("shipping", () => { const calculator = new ShippingCalculator(); // Testing private state instead of observable behavior expect(calculator._thresholds).toEqual([50, 100, 200]); expect(calculator._calculateInternal(75)).toBe(0); }); </example> <example title="The red-green cycle in practice"> // Step 1: Write the test FIRST — it fails because isEmail doesn't exist yet test("validates email format", () => { expect(isEmail("user@example.com")).toBe(true); expect(isEmail("not-an-email")).toBe(false); expect(isEmail("")).toBe(false); }); // Run: FAIL ✗ — ReferenceError: isEmail is not defined // This is correct. The test defines the contract.// Step 2: Write the minimum implementation function isEmail(input) { return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(input); } // Run: PASS ✓ — All three assertions pass
// Step 3: Run the full test suite to check for regressions </example>