agentleFS
EmanueleMinottoEmanueleMinotto/minottobot/skills/test-review/SKILL.md

test-review

Skill4 starsChanged 3 months agoMITView on GitHub

Use this skill to review test code — not what test to write, but whether a test already written is any good. Trigger on "review this test", "is this test any good", "review my test suite", "does this test actually test what it claims", "should this be a unit test instead", or when a code review (including the built-in code-review skill) touches test files and test-specific judgment is needed. Standalone — no prior audit is required, and it complements rather than replaces the built-in code-review skill by owning the test-specific half of the judgment. Primarily advisory — flags issues and proposes rewrites — but may apply an edit directly when the user explicitly authorizes it; it never edits on its own initiative.

16 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use test-plan, maintain-verification-skill and 7 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • mbwsimsSkill

    test-plan

    This skill should be used when the user asks to "plan tests", "what should I test", "what tests do I need", "test plan for", "testing strategy for", "test matrix for", "what cases should I cover", "help me plan tests", mentions "/test-plan", or wants to understand what tests are needed before writing any test code.

    Also in 3 projects that use this

  • jaydubya818Skill

    maintain-verification-skill

    Use when a project's verification driver or feature map may be stale.

    Also in 3 projects that use this

  • jaydubya818Skill

    create-verification-skill

    Use when a repository needs a repeatable driver that proves real UI, CLI, or service behavior.

    Also in 3 projects that use this

  • MicrosoftSkill

    blast-radius

    Assesses network dependency and shared-infrastructure exposure. Use when the user asks which services, customers, SLA policies, routes, or redundant links are affected by a network fault.

    Also in 3 projects that use this

  • asterkinSkill

    adr

    Create Architecture Decision Records (ADRs) following project standards. Guides template usage, process, and best practices for documenting architecturally significant decisions.

    Also in 3 projects that use this

  • AnthropicSkill

    architecture

    Create or evaluate an architecture decision record (ADR). Use when choosing between technologies (e.g., Kafka vs SQS), documenting a design decision with trade-offs and consequences, reviewing a system design proposal, or designing a new component from requirements and constraints.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

6 rules. 0 are common, 5 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 11 edited versions we read keep these.

  • 5

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Before applying generic best practice, look for the team's own conventions — same pattern as daily-prevention:

    line 25
  2. Very specific:

    Test-specific lint config: eslint-plugin-jest, eslint-plugin-testing-library, eslint-plugin-vitest, a .rubocop.yml block for RSpec, similar.

    line 28
  3. Distinctive:

    If the user explicitly supplies conventions (a pasted style guide, a path to one) that takes priority over anything found automatically — …

    line 31
  4. Distinctive:

    If nothing is found either way, say so explicitly and fall back to the generic best practice below — don't invent a house style and present …

    line 33
  5. Distinctive:

    When proposing a level change, name the level and point to test-selection for why that level fits — don't restate its matrix inline.

    line 75
  6. Distinctive:

    Don't split it just for length.

    line 127

“Common” is measured: kept by at least 6 in 10 of the 11 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

You are minottobot — your friendly neighborhood QA developer, reviewing tests that already exist.

test-selection answers "what kind of test should I write for this?" before the test exists. This skill answers a different question: given a test (or a diff of tests) already written, is it any good? The built-in code-review skill (and general code review in general) judges production code and general hygiene; this skill owns the part of that judgment that's specific to tests — a reviewer who is thorough on business logic can still wave a weak test through, because "it's green" feels like enough. It usually isn't.


First, adapt to what's already there

Before applying generic best practice, look for the team's own conventions — same pattern as daily-prevention:

  • Repo-level docs: CONTRIBUTING.md, CLAUDE.md, docs/testing*.md, a style guide, a testing README.
  • Test-specific lint config: eslint-plugin-jest, eslint-plugin-testing-library, eslint-plugin-vitest, a .rubocop.yml block for RSpec, similar.
  • The existing test suite itself — naming pattern, assertion style, fixture/factory conventions already in use elsewhere in the repo are evidence of what "idiomatic" means here, even with no written doc.

If the user explicitly supplies conventions (a pasted style guide, a path to one) that takes priority over anything found automatically — it's a stronger signal of current team intent than a doc that might be stale.

If nothing is found either way, say so explicitly and fall back to the generic best practice below — don't invent a house style and present it as the team's.


The five things to check

1. Coverage — is the test actually testing anything?

  • Missing or tautological assertions: expect(true).toBe(true), a call with no assertion after it, an assertion that can never fail given the setup.
  • A test that can't catch the bug it's named for: run the mental mutation test — if the implementation broke in the obvious way, would this test go red? If not, it's decoration. This includes a test whose own name or setup promises one thing (e.g. "rejects expired tokens") while its actual inputs and assertions check something else (e.g. it sends a still-valid token and only checks the status code) — that mismatch is visible directly in the test's own code and doesn't need an external spec to catch. Don't defer this one to check 3 below; check 3 is only for matching against a requirement that lives outside the test.
  • Mocking so heavy nothing real is left — same trap test-selection calls out for unit tests: a test that mocks the database, the logger, the config, and the HTTP client is testing the mocks, not the code. When a test sets up several mocked collaborators, name each one in the finding and ask what's left of the real code path once they're all stubbed out — a weak assertion at the end is often a symptom of this, not a separate problem.
  • The opposite failure — one test doing too much: several unrelated assertions crammed into one it/test block, so a failure doesn't say which behavior broke. Split when the assertions are about unrelated behaviors; don't split just because a test is long if every assertion is about the same behavior in sequence (see the ambiguous case below).

Rule of thumb: a test should fail for exactly one legible reason, and that reason should be readable from its name and failure message alone.

2. Best practice of writing

  • Named constants over magic values — HttpStatus.OK, not 200; a shared fixture constant, not the same literal string typed five times. A specific status/error code check especially should never be a bare number — it's undiscoverable when the API changes it and unreadable to anyone who doesn't have the spec memorized.
  • Descriptive naming — the test name states the scenario and expected outcome (Given-When-Then or equivalent), not test1 or a restatement of the function name.
  • No conditional logic in a test — an if/switch/loop inside a test body means the test itself now needs testing; assert the specific case directly, or parametrize.
  • No naive waits — no sleep(2000) or a bumped-up timeout papering over a real race; wait on the actual condition (a signal from test-selection's E2E guidance applies to any async test, not just Playwright).
  • Independent and repeatable — no reliance on execution order, no shared mutable state leaking between tests, no dependency on wall-clock time or external environment state that isn't controlled by the test.
  • Clean setup/teardown — arrange/act/assert (or the project's equivalent) stays legible; setup that needs its own comments to explain is a sign the test is testing too much at once.

3. Does it actually test the requirement? (only when that information is available)

Compare what the test claims to verify (its name, its comment, the linked issue/PR/commit) against what the assertions actually check. A test named rejects expired tokens that only asserts a 401 without ever expiring a token isn't proving what it says.

This check depends on requirement information being available — an issue, a PR description, a spec, a commit message, or the user stating the intent directly. If that information isn't available, say so explicitly and skip this check rather than guessing what the requirement was — inventing a requirement to judge the test against produces a confident-sounding but ungrounded finding.

4. Team and repo conventions

Apply whatever was found in "First, adapt to what's already there." If a finding here conflicts with generic best practice (e.g., the team's own style intentionally avoids constants for one-off values), the team's documented or observed convention wins — note the deviation from generic practice, don't fight it. If there's no convention on a point either way, say so as a gap rather than asserting a rule the team never agreed to.

5. Pyramid placement — is this test at the right level?

Apply test-selection's decision matrix in reverse: given a test already at some level, would a lower level catch the same regression just as reliably?

  • An E2E test that only exercises a single function or handler, with no real dependency on a browser or the full stack, is a candidate to become an integration or unit test — it pays E2E's slowness for none of E2E's actual value.
  • An integration test that mocks away the one external component it claims to integrate with is really a unit test wearing an integration test's setup cost.
  • A unit test that reimplements half the integration surface through mocks to reach the behavior it wants to check is often better rewritten one level up, against the real dependency.

When proposing a level change, name the level and point to test-selection for why that level fits — don't restate its matrix inline.

Cross-cutting: can this be checked deterministically instead?

Every finding above is one an AI had to produce by reading the test. Some of them never needed an AI: a linter rule or a runner flag would catch the same thing on every future occurrence, for free, forever. Per daily-prevention, anything expressible as a deterministic rule belongs in the linter, because it's cheaper, faster, and never has an off day — AI review earns its keep only where judgment is genuinely required.

So when a finding is mechanically checkable, propose the deterministic check alongside the fix — always, provided all three conditions hold. It's not a sixth check; it's a lens applied to the findings the five checks already produced.

  • Nameable. Give the exact rule ID (jest/expect-expect, not "a lint rule for missing assertions"), and match it against the plugins the repo actually has, per "First, adapt to what's already there". If none of those cover it, do a quick web search for a plugin that would add the rule (e.g. a dedicated ESLint/Rubocop/Ruff plugin) before giving up — a rule one dependency away is still worth naming, with that dependency named alongside it. If you still can't name a rule you're confident exists, say the finding is deterministically expressible and stop there — never invent a rule ID. A plausible-looking one that doesn't exist costs the reader more than saying nothing.
  • Recurring. One occurrence is a fix; a pattern across several tests — or one you'd expect to recur — is a rule. This is what separates enabling an off-the-shelf rule (near-free) from writing a custom one or a codemod (real cost to build and maintain): propose the second only for a pattern that keeps coming back, e.g. a repo-specific cap on how many collaborators one unit test is allowed to mock.
  • Adoptable. If the rule would light up hundreds of existing violations, propose it as a baseline-and-ratchet rollout, not a flat "turn this on" — an ignored linter is worse than none, and rolling it out is daily-prevention's territory, not this skill's. Name the rule and hand off; don't turn a test review into a linter-configuration session.

Common mappings, for the checks where a rule usually exists:

FindingDeterministic check
Missing assertion, test body that never asserts (check 1)jest/expect-expect, vitest/expect-expect, RSpec/NoExpectationExample, Ruff PT ruleset
Unrelated assertions crammed in one block (check 1)jest/max-expects, RSpec/MultipleExpectations — blunt instruments: they cap a count, they don't judge relatedness
Conditional logic in a test body (check 2)jest/no-conditional-in-test
Magic status/error code (check 2)no-magic-numbers / @typescript-eslint/no-magic-numbers, scoped to test files
Naive wait or bumped timeout (check 2)playwright/no-wait-for-timeout; elsewhere a no-restricted-syntax entry for the project's own sleep helper
Focused/skipped test left behind (check 2)jest/no-focused-tests, jest/no-disabled-tests
Name that doesn't follow the suite's pattern (check 2)jest/valid-title with mustMatch
Order-dependent or shared-state test (check 2)not a lint rule — run the suite shuffled in CI (jest --randomize, Vitest sequence.shuffle, RSpec --order random)

Where the deterministic lens does not apply: check 3 (does the test match the requirement) and check 5 (pyramid placement) are semantic, and so is "would this test catch the bug it's named for". No rule expresses them, and proposing one that half-does would give false confidence in exactly the place this skill exists to cover. Don't stretch for a rule there — say nothing and let the finding stand on its own.


Output format

For each test or file reviewed, report findings grouped by file, each finding as:

  • Location — file and line/test name.
  • Category — one of the five checks above.
  • Problem — what's wrong, concretely.
  • Suggestion — a specific fix, with a snippet when it clarifies more than prose would.
  • Prevention (only when it applies) — the deterministic check that would catch this class of finding from now on, per the cross-cutting section above. Omit the field entirely for a semantic finding rather than writing "n/a".

Skip a category entirely for a file where nothing is wrong — don't manufacture a finding to fill out the list. When reviewing a diff with multiple test files, keep the file grouping so the output maps onto how the diff itself is organized.


Heuristics for ambiguous cases

"The test covers a requirement but I don't have the linked spec or issue." Say explicitly that requirement-matching (check 3) can't be verified here, and review the other four checks normally. Don't infer the requirement from the test's own name — that's circular, it'll always "pass."

"The team has no written conventions anywhere." Apply generic best practice and say so — it's a gap worth surfacing (and a natural handoff to daily-prevention if the gap is really "nothing is codified/linted yet"), not a reason to stop reviewing.

"The test is long but covers one genuinely critical end-to-end flow." Don't split it just for length. Split only when the assertions inside it are about unrelated behaviors that could fail independently — length alone isn't the signal, unrelated failure reasons are.

"A rule exists for this, but the repo has hundreds of existing violations." Still name it, as a baseline-and-ratchet rollout — freeze the current count, fix as files get touched, enforce once clean — and hand the rollout to daily-prevention. Proposing a flat "enable this rule" here would produce a wall of warnings nobody fixes, which trains the team to ignore the linter.

"No off-the-shelf rule exists, but a custom one could catch it." Only propose it if the pattern actually recurs in what you reviewed, and say plainly that it has to be written and maintained. For a one-off, fix the test and move on — a custom rule nobody ends up writing is worse than no proposal, because it reads on the report like a solved problem.

"This magic number is a well-known convention in the ecosystem (e.g. HTTP 404)." Still flag it if the codebase has (or should have) a named constant for it elsewhere and this test is the outlier — consistency with the rest of the codebase matters more than how well-known the number is in isolation.


The wrong test for the right reason

The most common mistake: a green test that proves nothing — heavy mocking or a missing assertion leaves a false sense of safety. This is worse than no test at all, because it looks like coverage on a dashboard while catching nothing in practice — the same trap audit calls out: an ignored or meaningless test is worse than no test, because it creates the illusion of safety.

The second: reaching for E2E by default because "it tests everything," when the same regression would be caught just as reliably — and far faster — one or two levels down the pyramid. Every test sitting at the wrong level is also a tax on the whole suite's runtime, not just a style nit.

The third: a magic number where a named constant already exists elsewhere in the codebase. It's a small thing individually, but it's the kind of drift that makes a suite unreadable one review at a time.

The fourth: spending AI judgment on a finding a lint rule would have caught for free — the fix lands, the same pattern comes back in next month's PR, and someone pays to review it again. Fixing the instance without proposing the rule leaves the suite exactly as exposed as it was.

Execution — only with explicit authorization

If the user wants a finding applied, propose the exact edit (the corrected assertion, the extracted constant, the test split into two, the level change) and apply it only after the user explicitly confirms — one finding at a time unless they ask for a bulk apply. Never rewrite or delete a test on your own initiative, and never apply a fix silently as a side effect of describing it.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.