<< All versions

Skill v1.0.1

currentAutomated scan100/100
nbfrodri/agent-tack/testing

+6 new

──Details
PublishedOctober 14, 2026 at 08:37 PM
Content Hashsha256:01e45058cfce0b76...
Git SHAeff3420a87b5
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 6.1 KB)
SKILL.md6.1 KBactive
SKILL.md · 68 lines · 6.1 KB

version: "1.0.1" name: testing description: Write or review behavioral tests with pytest, Pest/PHPUnit and Vitest/Jest. Use for test coverage, fixtures, boundary mocks, database tests or flaky suites.


Testing

Tests exist so you can change code without fear. A good suite fails when behaviour breaks, passes when behaviour is preserved (even if the implementation changes), runs fast, and tells you exactly what broke. Tests that are coupled to implementation details, slow or flaky cost more than they protect.

For the TDD cycle itself see dev-workflow → references/tdd.md. For browser tests see the e2e-testing skill.

Framework-specific guidance (read the one the project uses):

  • Python (pytest, FastAPI, Django): references/python.md
  • PHP / Laravel (Pest, PHPUnit): references/php.md
  • JavaScript / TypeScript (Vitest, Jest, Testing Library, Node APIs): references/javascript.md

Before writing tests

Find the project's test commands (README, manifests or CI) and establish the relevant baseline: reuse a recent result for unchanged inputs, otherwise run the affected suite. Broaden when the project requires it or the change's risk justifies it. Match the existing framework, layout, factories and helpers; don't introduce a second test framework.

What to test at each layer

LayerTest typeFocus
Domain (entities, value objects, pure functions)Unit, no I/OBusiness rules, invariants, edge cases. Most tests live here.
Use cases / servicesUnit with fakes for ports, or integrationOrchestration, errors, side effects requested (events, emails)
Repositories / queriesIntegration with a real DBQueries return the right data, constraints, migrations
HTTP endpoints / Server ActionsIntegration (API tests)Status codes, validation (422), auth (401/403), response shape
UI componentsComponent testsWhat the user sees and does, all four states (loading, error, empty, success)
Critical journeysE2E (few)Signup, login, checkout… end to end

Don't test the framework or libraries (that Django saves a model, that React renders a div), trivial getters, or private methods directly (test them through the public API).

Choosing cases

For each unit of behaviour, cover:

  • the happy path;
  • boundaries: empty, zero, one, many, max, off-by-one dates and time zones;
  • invalid input and each error path;
  • permissions (allowed, denied, someone else's resource);
  • any bug that was fixed (a regression test named after the bug).

Use parametrised or table tests when the same behaviour is checked with many inputs.

Writing them well

  • Name = behaviour: test_rejects_order_when_stock_is_insufficient, it('shows an error when the email is taken'). The name should read like a spec.
  • Arrange / Act / Assert, visibly separated, one behaviour per test. Several assertions are fine if they check the same behaviour.
  • Assert on outcomes, not on implementation: returned values, state, HTTP responses, rendered output, messages sent. Avoid asserting that internal methods were called.
  • Test data via factories/builders with sensible defaults, overriding only what matters to the test, so each test shows what's relevant. Avoid giant shared fixtures and fixture dumps.
  • Deterministic: freeze time, seed randomness, no real network, no dependence on test order or on data left by other tests.
  • Self-contained: a reader understands the test without jumping through many helpers. Some duplication in tests is fine if it keeps them clear.

Mocks and fakes

Mock at the boundaries you don't own or that are slow or non-deterministic: HTTP to third parties, email/SMS, payment gateways, the clock, randomness, queues. Prefer fakes (in-memory repository, fake mailer) over mocks with call expectations. Don't mock the thing under test, value objects, or your own domain classes. For HTTP, intercept at the network layer (respx, Http::fake(), MSW/nock) rather than patching internal functions.

Database tests

Use the same engine as production (testcontainers or a docker-compose service). Isolate each test with a transaction rollback or truncation, create data with factories, and assert query counts on important endpoints to catch N+1 regressions.

Legacy code without tests (characterisation tests)

Before changing untested code, pin down what it does now: call it with representative inputs, record the actual outputs in assertions, and keep those tests while refactoring. If the current behaviour looks wrong, don't silently "fix" the expectation: note it, tell the user, and mark the test clearly (e.g. a xfail/todo with the reason). Break dependencies with seams (inject the clock, the HTTP client, the repository) to make code testable, in small, separate refactoring commits.

Coverage

Coverage shows what is not tested; it doesn't prove the tests are good. Aim for high coverage on domain and use-case code; don't chase 100% on glue code. Look at uncovered branches in changed files, not just the global percentage. Mutation testing can check whether assertions catch selected defects when worth the time; code and design still need review.

Flaky and slow tests

A flaky test is a bug, either in the test or in the code (race conditions). Reproduce it by running it many times or in random order, find the source of non-determinism (time, ordering, shared state, async waits, network), and fix it. Don't just add retries or sleeps. For slow suites, profile them (pytest --durations, --profile, Vitest reporters), move logic tests down to unit level, share expensive setup carefully, and run in parallel.

Finishing

Run the affected tests and relevant lint/type checks. Run the full suite when required by the project or justified by risk; don't repeat a passing check for unchanged inputs without new evidence. Report checks actually run and remaining gaps. A behavioral test you wrote must have been seen failing for the right reason at least once, either before implementation (TDD) or by temporarily reintroducing the defect. Commit tests with the code they cover (feat/fix), or as test(<scope>): … when only adding tests.

← v1.0.0All versions