seed-skills/tdd-patterns/SKILL.md
Practice strict red-green-refactor test-driven development — write one failing test first, make it pass with the minimum code, then refactor under green, with worked cycles in Jest and pytest, AAA structure, and behavior-based test naming.
npx skillsauth add PramodDutta/qaskills TDD PatternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill makes an AI agent develop features test-first: write exactly one failing test, watch it fail for the right reason, write the minimum production code to pass, then refactor while green. It enforces the discipline most "TDD" sessions skip — never writing production code without a failing test demanding it. Trigger it when the user asks for TDD, test-first development, or when implementing new logic in a codebase that already has a test runner wired up.
src/.rejects expired coupons at the boundary minute tells the next reader the rule; test_coupon_3 tells them nothing.Feature: a PriceCalculator that applies tiered bulk discounts.
RED — write the smallest failing test:
// src/price-calculator.test.ts
import { describe, expect, it } from '@jest/globals';
import { calculateTotal } from './price-calculator';
describe('calculateTotal', () => {
it('returns unit price times quantity with no discount under 10 units', () => {
// Arrange
const unitPrice = 4.0;
const quantity = 3;
// Act
const total = calculateTotal(unitPrice, quantity);
// Assert
expect(total).toBe(12.0);
});
});
npx jest price-calculator
# FAIL — Cannot find module './price-calculator' <- failing for the RIGHT reason
GREEN — minimum code, no speculation:
// src/price-calculator.ts
export function calculateTotal(unitPrice: number, quantity: number): number {
return unitPrice * quantity;
}
RED again — the next test forces the discount rule:
it('applies a 10 percent discount at 10 units or more', () => {
expect(calculateTotal(4.0, 10)).toBe(36.0); // 40 - 10%
});
it('applies a 20 percent discount at 50 units or more', () => {
expect(calculateTotal(2.0, 50)).toBe(80.0); // 100 - 20%
});
GREEN:
export function calculateTotal(unitPrice: number, quantity: number): number {
const subtotal = unitPrice * quantity;
if (quantity >= 50) return subtotal * 0.8;
if (quantity >= 10) return subtotal * 0.9;
return subtotal;
}
REFACTOR — under green, extract the tier table:
const DISCOUNT_TIERS: ReadonlyArray<{ minQty: number; multiplier: number }> = [
{ minQty: 50, multiplier: 0.8 },
{ minQty: 10, multiplier: 0.9 },
{ minQty: 0, multiplier: 1.0 },
];
export function calculateTotal(unitPrice: number, quantity: number): number {
const tier = DISCOUNT_TIERS.find((t) => quantity >= t.minQty)!;
return unitPrice * quantity * tier.multiplier;
}
Run the suite after the refactor. Still green, behavior unchanged, structure improved. That is one complete cycle.
Feature: a password strength validator, driven boundary-first.
# tests/test_password_policy.py
import pytest
from app.password_policy import validate
class TestValidate:
def test_rejects_passwords_shorter_than_12_chars(self):
# Arrange / Act
result = validate("Short1!aaaa") # 11 chars
# Assert
assert result.ok is False
assert "at least 12 characters" in result.errors
def test_accepts_a_12_char_password_meeting_all_rules(self):
result = validate("Sturdy-Pass1") # exactly 12
assert result.ok is True
assert result.errors == []
pytest tests/test_password_policy.py -x
# ModuleNotFoundError: No module named 'app.password_policy' <- correct red
Minimum green:
# app/password_policy.py
from dataclasses import dataclass, field
@dataclass
class Result:
ok: bool
errors: list[str] = field(default_factory=list)
def validate(password: str) -> Result:
if len(password) < 12:
return Result(ok=False, errors=["at least 12 characters"])
return Result(ok=True)
Next red drives the remaining rules — and parametrize keeps each rule one logical test:
@pytest.mark.parametrize(
("password", "missing"),
[
("alllowercase-12", "an uppercase letter"),
("ALLUPPERCASE-12", "a lowercase letter"),
("NoDigitsHere-Ab", "a digit"),
],
)
def test_reports_each_missing_character_class(self, password, missing):
result = validate(password)
assert result.ok is False
assert missing in result.errors
Green, then refactor the rule checks into a table of (predicate, message) pairs — same move as the discount tiers above.
When one example lets you fake it (return 36.0), add a second example with different inputs. Two data points force the general implementation; that is exactly when to generalize, not before.
Every test reads as three blocks separated by blank lines. One Act per test. If you need a second Act, you need a second test.
Before starting, jot the behaviors as comments; convert one at a time into a real failing test:
// TODO test list — price-calculator
// [x] no discount under 10 units
// [x] 10% at 10+
// [x] 20% at 50+
// [ ] rejects negative quantity with RangeError
// [ ] rounds to 2 decimal places (0.1 + 0.2 money bugs)
Write the boundary test (exactly 10 units, exactly 12 chars) before the comfortable middle. Off-by-one bugs live at boundaries; TDD that skips them certifies nothing.
jest price-calculator --watch, pytest -x -k password); run the full suite before commit.git commit after each cycle gives you a bisectable history and a free undo for failed refactors.expect(repo.save).toHaveBeenCalled() as the only assertion). Verify observable outcomes; interaction-only tests pass while behavior is broken.development
Build WebdriverIO E2E suites — wdio.conf.ts setup, $ and $$ selectors, auto-wait and waitUntil, Mocha framework structure, page objects, parallel capabilities, and services for visual testing and Appium mobile.
testing
Test Vue 3 components with Vue Test Utils and Vitest — mount vs shallowMount, finding and triggering DOM, asserting props and emitted events, awaiting async updates, and mocking Pinia stores and Vue Router.
testing
Write fast unit and integration tests with Vitest — vitest.config.ts setup, vi.fn and vi.mock module mocking, fake timers, snapshots, V8 coverage with thresholds, workspaces for monorepos, and in-source testing.
development
Test Node.js HTTP APIs in-process with SuperTest — request(app) without binding a port, chained .expect assertions, auth headers, JSON body validation, and Jest integration with proper async/await patterns.