mcpField
T
testing

testing

github.com/iulianditaTesting & quality1049 installsUpdated today
Repository

About testing

testing is a software testing skill for writing, structuring, and maintaining tests across unit, integration, E2E, accessibility, and performance layers. It emphasizes proportional suites that catch regressions without optimizing for coverage counts.

Key features

  1. 01Guidance across test layers
  2. 02Proportional test selection
  3. 03Framework-aware test patterns
  4. 04Flaky test diagnosis
  5. 05Fixture, mocking, and test-data guidance

Use cases

  1. 01Adding regression tests for a bug fix
  2. 02Choosing tests for a feature change
  3. 03Diagnosing flaky or failing tests
  4. 04Planning test infrastructure and coverage gates

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/iuliandita/skills --skill testing

After installing, type/testingin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
agent-hygiene.md
e2e-accessibility.md
language-patterns.md
output-contract.md

Details

From the project README

Testing: Write Tests That Catch Real Bugs

Write, structure, and maintain tests across unit, integration, E2E, accessibility, and performance layers. The goal is tests that catch regressions, document behavior, and run fast in CI - not tests that exist to inflate coverage numbers.

Target versions (October 2026):

  • Vitest 5.0.5, Jest 30.5.2
  • Playwright 1.64.1, Cypress 16.1.1 (both Vitest and Cypress are major upgrades; review migration notes)
  • pytest 9.2.0, pytest-cov 7.1.0
  • Go 1.27.3 (testing stdlib, testing/synctest GA)
  • Rust 1.99.0 (cargo test, cargo-nextest 0.9.150)
  • Testing Library 16.3.3 (@testing-library/react)
  • axe-core 4.14.0 (@axe-core/playwright)
  • Grafana k6 2.3.0

Security check (2026-10-28): GHSA-2h32-95rg-cppp is critical browser-runner script injection that can expose the API token and lead to local code execution. It affects @vitest/browser >=4.0.17,<4.1.6 and >=5.0.0-beta.0,<5.0.0-beta.3; fixes are 4.1.6 and 5.0.0-beta.3. Keep browser packages aligned with the stable runner and do not open untrusted runner URLs while the server is active. Later critical browser-mode advisories (GHSA-g8mr-85jm-7xhm, GHSA-p63j-vcc4-9vmv) raise the 4.x floor to 4.1.10 and the 3.x floor to 3.2.7. October 21 advisories raise them again: GHSA-p458-7748-mcp3 (server.fs.deny bypass serving .env, keys, and the API token; @vitest/ui/@vitest/browser <5.0.5) and GHSA-qxg6-89mg-rqv8 (mock-registration code injection; @vitest/mocker <4.1.12 and 5.0.x <5.0.3). Floors are 5.0.5 and 4.1.12; 3.x has no patched release, so move off it.

When to use

  • Writing new tests (unit, integration, E2E, accessibility, performance)
  • Debugging flaky or failing tests
  • Designing test architecture for a project (fixture strategies, factory patterns, test data)
  • Setting up test infrastructure in CI (parallelization, sharding, coverage gates)
  • Choosing testing tools or migrating between test frameworks
  • Choosing the smallest test set that protects a change
  • Adding accessibility or visual regression tests to an existing suite

When NOT to use

  • Profiling and optimizing an identified application bottleneck - use performance-debugging.
  • Broker acknowledgement, redelivery, dead-letter, or replay semantics - use message-queues.
  • Reviewing existing test quality or correctness as part of a code review - use code-review
  • Security-specific testing (penetration testing, OWASP checks) - use security-audit
  • Cleaning up verbose/sloppy test code - use code-simplification
  • Ad-hoc web browsing, scraping, or page interaction outside of tests - use the host's browsing tools
  • CI/CD pipeline architecture (test jobs run inside pipelines, but pipeline design is ci-cd's domain) - use ci-cd
  • Database testing patterns at the engine level - use databases
  • Writing or refining LLM prompts (use prompt-generator)
  • Infrastructure or configuration validation outside tests (use terraform, ansible, or kubernetes)
  • AI/ML model evaluation or LLM output scoring - use llm-app-development
  • Infrastructure-level load or chaos testing beyond application tests (use kubernetes for cluster-level chaos, or ci-cd for pipeline-integrated load test orchestration)

Proportionality

Size the suite to the change. This overrides any urge to test every function or to chase a count.

  • One test per behavior, plus the realistic failure modes (bad input, boundary, error path). Distinct inputs of one behavior are one parametrized test.
  • Skip tests for glue, wiring, config, getters, and behavior the framework or type system already guarantees.
  • Past ~5 new tests, or when the tests are longer than the code under test, state why each earns its place or cut.
  • Bug fix: one regression test that fails before the fix. Not a suite.
  • Prefer the fewest tests that would fail if the behavior broke.

AI Self-Check

AI tools consistently produce the same testing mistakes. Before returning any generated test code, verify against this list:

  • Tests assert behavior, not implementation - no testing private methods or internal state
  • Each test has exactly one reason to fail (single assertion concept, not single assert call); distinct inputs of the same behavior go in one parametrized test, not separate tests
  • Test count is proportional to the change (see Proportionality); no near-duplicates, no tests of glue or framework behavior
  • Test names describe the scenario and expected outcome, not the method name
  • Mocks/stubs are scoped to the test - no shared mutable mock state across tests
  • No hardcoded ports, paths, or timestamps that break on other machines or in CI
  • Async tests properly await all promises/futures - no fire-and-forget assertions
  • Test data is isolated - each test creates its own state, no dependency on test execution order
  • Cleanup happens even when assertions fail (use afterEach/teardown/t.Cleanup/Drop)
  • No sleep() or fixed delays for async waits - use polling, retries, or event-based waits
  • Coverage threshold is realistic (80% line coverage is a good default; 100% is a lie), and coverage was not the reason any test was added
  • Snapshot tests have been reviewed manually before committing (blind --update is a bug factory)
  • E2E selectors use role or accessible name first, data-testid as a last resort - not CSS classes or DOM structure
  • Runner APIs current: pytest, Vitest, Jest, Playwright, and Testing Library examples match current runner behavior
  • Flake source identified: retries are not used to hide nondeterminism without diagnosis
  • Cross-cutting agent hygiene applied - see references/agent-hygiene.md

Performance

  • Split fast unit tests from integration, browser, and performance suites.
  • Use fixtures and test data builders to avoid repeated expensive setup.
  • Shard or parallelize only after isolating shared state, ports, databases, and clocks.

Workflow

Copy this checklist and track progress:

  • Step 1: Scope and existing framework identified
  • Step 2: Test layer chosen
  • Step 3: Test written (for a bug fix, confirmed failing before the fix)
  • Step 4: Affected checks pass; failures not caused by the behavior under test fixed via Step 3
Step 1: Determine scope

Based on context:

  • New feature -> test the behaviors that matter, alongside or after the code; not every function
  • Bug fix -> write one failing test first that reproduces the bug, then fix
  • Existing untested code -> prioritize critical paths, not 100% coverage
  • Test infrastructure -> set up runners, CI config, coverage gates

Identify the project's existing test framework from config files (vitest.config.ts, jest.config.*, pyproject.toml, Cargo.toml, *_test.go, playwright.config.ts). Match it. Don't introduce a second test runner without a reason.

Step 2: Choose the test layer
LayerTests whatSpeedWhen to use
UnitSingle function/module in isolationmsPure logic, utilities, data transforms, state machines
IntegrationMultiple modules, real dependenciessecondsAPI handlers, database queries, service boundaries
E2EFull user flows through the UIseconds-minutesCritical paths, checkout flows, auth, onboarding
AccessibilityWCAG compliance, screen reader compatsecondsUser-facing components/pages the change touches
VisualScreenshot comparisonsecondsUI components after style changes
PerformanceLoad, latency, throughputminutesBefore releases, after arch changes

The pyramid is about where a test sits, not a quota. Push each check to the cheapest layer that would still catch the failure; do not write tests to fill the layers. Invert it (slow E2E checks for logic a unit test covers) and CI takes 45 minutes and everyone ignores failures. Prefer the fewest tests that would fail if the behavior broke.

Step 3: Write the test

Follow the language-specific patterns below. Universal principles:

Arrange-Act-Assert (or Given-When-Then):

// Arrange: set up test data and dependencies
// Act: call the thing being tested
// Assert: verify the outcome

Test naming: describe the scenario, not the function.

# Bad:  test_calculate_total
# Good: test_calculate_total_applies_discount_when_cart_exceeds_100
# Good: it("returns 401 when token is expired")
Step 4: Validate
  • Run checks appropriate to the changed behavior and every required repository gate. Run the full suite when required or when the affected surface warrants it; do not add tests that mirror reversible, low-impact edits. Repeat or broaden passing checks only for new changes, failures, or unresolved concerns.
  • If a new test fails for a reason other than the behavior under test, fix the test or fixture and return to Step 3
  • Check coverage delta as a signal for missed behavior, not a target: new code is not a reason to chase a number, and coverage is never a reason to add a test
  • Run in CI if possible - tests that pass locally but fail in CI are the worst kind

TDD Workflow

Optional. Use red-green for bug fixes and non-trivial logic where the behavior is well-defined upfront. It does not mean no code without a test or a test for every function. Skip it when exploring or prototyping.

  1. Red: write a test that fails (confirm it fails for the right reason)
  2. Green: write the minimum code to make the test pass (ugly is fine)
  3. Refactor: clean up without changing behavior (tests still pass)

TDD works best for: pure functions, data transformations, state machines, API contracts, bug reproduction.

TDD works poorly for: UI layout, exploratory prototyping, integration with undocumented APIs.


Mocking Strategy

Mock at boundaries, not everywhere. Over-mocking produces tests that pass while the real code is broken.

What to mockWhat NOT to mock
External APIs (HTTP, gRPC)Your own pure functions
Database (when unit testing)Data transformations
Time/dates, random valuesSimple utility code
File system (when impractical)The module under test
Third-party SDKsStandard library functions

Prefer fakes over mocks when possible. An in-memory database implementation tests more real behavior than a mock that returns canned responses.

Injectable clock for TTL/time-dependent tests - pass a clock dependency rather than calling Date.now() or time.Now() directly:

// Production: clock = () => Date.now()
// Test: clock = () => FIXED_TS + offset
function isExpired(createdAt: number, ttlMs: number, clock = Date.now): boolean {
  return clock() - createdAt > ttlMs;
}
// In test: advance virtual time without sleeping
const fakeNow = vi.fn().mockReturnValue(START);
expect(isExpired(START, 1000, fakeNow)).toBe(false);
fakeNow.mockReturnValue(START + 1001);
expect(isExpired(START, 1000, fakeNow)).toBe(true);

For cached fetches, cover the observable paths (one parametrized or sequential test when they share setup):

  • Cache miss: the HTTP boundary is called once and the returned value is cached.
  • Cache hit: the cached value is returned and the HTTP boundary is not called.
  • TTL expiry: advance an injected or fake clock, then assert one refresh instead of sleeping.

Read references/language-patterns.md for language-specific mocking idioms (Vitest vi.mock, Jest jest.mock, pytest monkeypatch, Go interfaces, Rust trait objects).


Test Data and Fixtures

Factory pattern (preferred)

Build test data with sensible defaults and per-test overrides:

// TypeScript - factory function
function buildUser(overrides: Partial<User> = {}): User {
  return { id: randomUUID(), name: "Test User", email: "[email protected]", ...overrides };
}

// Python - factory function
def build_user(**overrides) -> User:
    defaults = {"id": uuid4(), "name": "Test User", "email": "[email protected]"}
    return User(**(defaults | overrides))
Fixture rules
  • Isolate per test. Shared mutable fixtures cause order-dependent failures.
  • Use builders/factories over raw object literals - defaults prevent test brittleness.
  • Database fixtures: use transactions that roll back after each test (pytest db fixture, Jest beforeEach with rollback). Seeded test databases beat shared staging data.
  • File fixtures: use temp directories (tmp_path in pytest, os.MkdirTemp in Go, tempfile in Rust). Clean up in teardown.

Accessibility Testing

Catch WCAG violations automatically. Not a replacement for manual testing, but catches the mechanical stuff (missing alt text, broken ARIA, contrast ratios, keyboard traps).

Use @axe-core/playwright (if missing, add it as a dev dependency with the repo's package manager: npm install -D, pnpm add -D, yarn add -D, or bun add -d @axe-core/playwright) - run new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]).analyze() and assert zero violations. Scope axe scans to the pages or components the change touches, not the whole app on every change. Exclude known issues with .exclude() and track them as tech debt, not permanent exceptions.

Own repeatable accessibility regression checks here; use frontend-design to repair interaction design and semantics. Record manual keyboard and screen-reader coverage separately from automated scan results.

Read references/e2e-accessibility.md for Playwright E2E patterns, visual regression setup, and CI accessibility gates.


Performance Testing

Two categories: micro-benchmarks (is this function fast enough?) and load tests (does the system handle traffic?).

Micro-benchmarks
  • Go: func BenchmarkX(b *testing.B) - built into the stdlib
  • Rust: cargo bench with criterion (criterion = "0.8")
  • JS/TS: vitest bench (use tinybench directly outside Vitest)
  • Python: pytest-benchmark (timeit for a one-off measurement)
Load testing (k6)

Detect first: command -v k6 >/dev/null || echo "k6 missing".

// k6 load test
import http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  stages: [
    { duration: "30s", target: 50 },   // ramp up
    { duration: "1m",  target: 50 },   // sustain
    { duration: "10s", target: 0 },    // ramp down
  ],
  thresholds: {
    checks: ["rate==1"],              // fail the run when any status check fails
    http_req_duration: ["p(95)<500"],   // 95th percentile under 500ms
  },
};

export default function () {
  const res = http.get("http://localhost:3000/api/health");
  check(res, { "status 200": (r) => r.status === 200 });
  sleep(1);
}

Don't run load tests against production without explicit approval. Don't run them in CI unless you have dedicated infrastructure for it.


CI Integration

Test parallelization
  • Vitest/Jest: built-in worker parallelism. Vitest uses Vite's module graph for smart test file distribution.
  • Playwright: --shard=1/4 for splitting across CI runners. --workers=4 for parallel within a runner.
  • pytest: pytest-xdist with -n auto for CPU-based parallelism.
  • Go: go test -parallel N per package, -p N for package-level parallelism.
  • Rust: cargo nextest run for per-test process isolation and parallelism (cargo nextest --version >/dev/null 2>&1 || echo "cargo-nextest missing: cargo install cargo-nextest").
Flaky test management

Flaky tests erode trust. Fix or quarantine immediately.

  1. Identify: track test stability over time (most CI systems have flaky test dashboards)
  2. Quarantine: move to a separate job that doesn't block merges. Tag with @flaky or skip.
  3. Fix root causes - common culprits by framework:
    • Playwright/Cypress: race conditions on navigation or animation. Use waitForLoadState, waitForSelector, or Playwright's auto-waiting. Avoid page.waitForTimeout. Stub network requests to eliminate backend variability. Create a fresh browser context per test so cookies, storage, and service workers cannot leak between cases. Headless mode (CI) has different rendering timing than headed. Reproduce with the same headless browser, OS image, viewport, fonts, and worker count as CI first; compare --headed only to isolate rendering differences. Check CPU, memory, and worker contention on the CI runner before changing timeouts.
    • Vitest/Jest: shared module state between test files. Use --pool forks (Vitest) or --runInBand (Jest) to isolate. Check for leaked timers (vi.useFakeTimers not restored).
    • pytest: database state leaking between tests. Use @pytest.mark.usefixtures("db") with transactional rollback. Check for global state mutation in fixtures.
    • Go: t.Parallel() tests sharing package-level state. Use t.Cleanup for teardown. Check for goroutine leaks with goleak.
  4. Retry with caution: --retries 2 (Playwright) or --reruns 2 (pytest-rerunfailures) is a bandaid, not a fix
Coverage thresholds

Keep these as CI gate guidance for projects that already set coverage gates; otherwise do not introduce one for a small change. Reasonable defaults:

MetricThresholdWhy
Line coverage80%Catches obvious gaps
Branch coverage70%Catches untested conditions

A threshold is a CI gate, not a goal: never add a test just to move the number. Enforce via vitest --coverage --coverage.thresholds.lines=80, pytest --cov --cov-fail-under=80, or go test -coverprofile + threshold script.

Minimal CI example (pytest + GitHub Actions):

- run: pip install pytest pytest-xdist pytest-cov
- run: pytest -n auto --cov=src --cov-fail-under=80 --tb=short

Reference Files

  • references/language-patterns.md - language-specific test patterns for JS/TS (Vitest, Jest), Python (pytest), Go (testing stdlib), and Rust (cargo test). Covers mocking, table-driven tests, async testing, snapshot testing, and framework-specific idioms.
  • references/e2e-accessibility.md - E2E testing with Playwright, visual regression (screenshot comparison, component snapshots), accessibility testing patterns, and CI integration for browser tests.

Output Contract

See references/output-contract.md for the full contract.

  • Skill name: TESTING
  • Deliverable bucket: audits
  • Mode: conditional. When invoked to analyze, review, audit, or improve existing repo content, apply the reporting size and evidence rules in references/output-contract.md and write the deliverable to docs/local/audits/testing/<YYYY-MM-DD>-<slug>.md. When invoked to answer a question, teach a concept, build a new artifact, or generate content, respond freely without the contract.
  • Severity scale: P0 | P1 | P2 | P3 | info (see shared contract; only used in audit/review mode).

Related Skills

  • code-review - reviews test quality and correctness as part of code reviews. This skill writes the tests; code-review evaluates whether they actually test the right things.
  • security-audit - handles security-specific testing (OWASP, penetration testing, credential scanning). This skill handles functional testing.
  • code-simplification - reviews verbose, over-abstracted, or AI-generated test code for safe reductions. If the test works but reads like a novel, route to code-simplification.
  • ci-cd - designs the pipeline that runs tests. This skill writes the tests and configures test runners; ci-cd handles the pipeline structure around them.
  • databases - covers database engine testing and configuration. This skill handles application-level database test patterns (transactions, fixtures, test data).
  • llm-app-development - AI/ML model evaluation, LLM output scoring, and benchmark harnesses. This skill handles functional application testing; llm-app-development handles model-level evaluation.
  • kubernetes - cluster-level chaos, resilience, and infrastructure-layer load testing. This skill handles application test code; kubernetes handles cluster-level fault injection.

Rules

The AI Self-Check holds the per-test guards (behavior over implementation, no sleep(), isolated state, flake diagnosis, reviewed snapshots). This adds:

  • Don't test the framework. Testing that React renders a div, or that Express routes to a handler, is testing someone else's code. Test YOUR logic.
  • Test bloat. Near-duplicate tests of one behavior, tests that restate the implementation, and over-mocked tests. See Proportionality.

Comments

Markdown supported