Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Test pyramid decision matrix, coverage targets, when to write which test type, mock vs real dependency decisions, and test ROI analysis.
.claude/skills/test-strategy/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-11 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
/\
/e2e\ ~10% — critical user flows only
/------\
/ integ \ ~20% — API contracts, DB interactions
/------------\
/ unit \ ~70% — pure logic, transformations, edge cases
/--------------\Keep the pyramid right-side-up. Inverting it (too many e2e) leads to slow, flaky CI.
| Task Type | Test Type | Tool | |-----------|-----------|------| | Pure function / utility | Unit | Jest / Vitest | | API endpoint | Integration | Supertest / httpx | | Critical user flow | E2E | Playwright | | Data transformation | Property-based | fast-check / Hypothesis | | React/Vue component | Component | Testing Library | | CLI command | Integration | execa + assertions | | Database query | Integration (real DB) | jest + pg / pytest | | Cron job / scheduler | Unit (mocked time) | Jest fakeTimers |
Is it an external API (Stripe, Sendgrid, etc.)?
→ YES: Always mock. Use recorded fixtures or MSW.
Is it a database?
→ Unit test context: mock (in-memory store or jest.fn())
→ Integration test context: real DB (test container or local)
Is it the file system?
→ Mock with memfs or tmp dir, then clean up.
Is it time / Date.now()?
→ Always mock. Use Jest fakeTimers or freezegun (Python).
Is it a third-party SDK wrapper you wrote?
→ Skip testing the wrapper itself, test your code's behavior.| Project Type | Branch Coverage | Notes | |-------------|----------------|-------| | Published library | 90%+ | Every exported function needs tests | | Production app | 80%+ | Focus on critical paths | | Internal tool | 70%+ | Happy path + main error cases | | Prototype / spike | Skip | Throw it away anyway | | Generated code | Skip | Don't test codegen output |
typescriptdescribe('calculateDiscount', () => { it('returns 10% for gold members', () => { ... }) it('returns 0% when cart is empty', () => { ... }) it('throws when discount rate exceeds 100', () => { ... }) })
typescriptdescribe('OrderService', () => { describe('given a confirmed order', () => { describe('when the user cancels', () => { it('then it transitions to CANCELLED state', () => { ... }) it('then it sends a cancellation email', () => { ... }) }) }) })
getEmail() { return this.email })_app.tsx, Express server bootstrap)typescriptbeforeEach(async () => { await db.query('BEGIN') }) afterEach(async () => { await db.query('ROLLBACK') })
typescriptafterEach(() => { jest.clearAllMocks() // clear call counts jest.resetAllMocks() // reset return values jest.restoreAllMocks() // restore spied originals })
typescriptimport { PostgreSqlContainer } from '@testcontainers/postgresql' let container: StartedPostgreSqlContainer beforeAll(async () => { container = await new PostgreSqlContainer().start() process.env.DATABASE_URL = container.getConnectionUri() }) afterAll(async () => { await container.stop() })
When a test is flaky (passes/fails non-deterministically):
await on async callssetTimeout, Date.now())--runInBand to isolate and confirmMutation testing verifies that your tests actually catch bugs:
bashnpx stryker run
json// stryker.config.json { "mutator": { "excludedMutations": ["StringLiteral"] }, "thresholds": { "high": 80, "low": 60, "break": 50 }, "reporters": ["html", "progress"] }
Mutation score < 60% means tests pass without catching real logic errors. Focus on the surviving mutants — each one is an untested code path.
High ROI (write these first):
Low ROI (write last or skip):
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-24 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +25 percentage points is the difference between those two pass rates over the 24 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.