Testing Testing v1.0.0

Grade Tests

Grade curated individual tests for readiness, A-F quality and improvements. ALWAYS USE FOR: grade tests, review only a named test, per-test readiness or quality bands for supplied methods, bodies, file spans or bounded PR diffs, including existing tests. Return Pass/Failed/Uncertain/Not applicable; unresolved/empty scopes omit grades. Compose read-only per-test evidence. Polyglot. DO NOT USE FOR: suite audits (test-engineer/test-anti-patterns), concrete fix/feature detection proof (test-value-evidence), writing/fixing tests, or coverage.

Workflow

Step 1: Detect language and load extension

Identify the target codebase's language and test framework from the file extensions and the test method markers in the provided list. Read the matching caller-provided extension file directly; otherwise read the matching extensions/ file relative to the runtime-listed test-analysis-extensions catalog (dotnet.md, python.md, typescript.md, go.md, etc.). Use the catalog for other languages, not a skill invocation. If the input contains tests from multiple languages, load each relevant extension and grade each test using its language's conventions.

Step 2: Resolve the test bodies

For each entry in the input list:

  1. If the test body is provided inline, use it directly.
  2. Otherwise read the file at the given path and locate the method by its

fully-qualified name. Capture the full method body, including attributes / decorators / fixtures and any helper code that the test calls.

  1. If a requested method cannot be found, record it as

Uncertain — method not found with no quality grade and continue. Never invent a body to grade. A missing requested method requires human review; it is not the same as a valid scope containing no tests.

Composition checkpoint: for resolved tests with available production context, load test-gap-analysis now, once for the batch, with per-test-read-only assessment context. Complete its owned reference assessment before Step 3. Do not skip this load just because a body-level weakness already seems obvious; a locally invented mutation explanation is not composition.

Step 3: Assess the claimed behavior and score each resolved test

Keep grading read-only: no build/test runs, mutation execution, file edits, tool installation, broad suite discovery, or agent delegation. Resolve only the supplied tests, their relevant fixtures/helpers, and the production call chain needed for their claims.

Use the inline test-gap-analysis assessment from Step 2's checkpoint; do not load it a second time. Supply each test's identifier/body, relevant setup/helpers, claimed behavior, assertion semantics, and available source. Its composition dispatch loads the owned read-only reference rather than its standalone baseline/verification workflow. Consume its per-test evidence; do not duplicate its mutation catalog here or invoke an audit/generation agent. Convey mode and inputs as assessment context using the host's supported caller instructions. If the loader accepts only a skill name, load test-gap-analysis by name only; do not invent tool arguments or a mode-specific skill name.

If the skill/reference or production context is unavailable, record Pseudo-mutation: N/A / unverified — <reason> and continue normal body-level grading. This is not a grade deduction or, by itself, an Uncertain result. Do not search installation directories or substitute a mutation runner.

Assess only what each test claims: do not borrow another test's assertions, or demand unrelated branches, outputs, or scenarios. An observable survivor can support an existing Assertion strength category when it proves that the test does not verify its claimed outcome; do not introduce mutation points, weights, ceilings, or an automatic survivor penalty. Apply the existing rubric normally, including weaknesses it classifies in both Assertion and Anti-pattern dimensions; do not add another deduction for the same mutation evidence.

Start every test at grade A (score band 90–100), then apply deductions strictly for observable issues in the captured body. Do not deduct for hypothetical concerns (e.g., "could have more negative assertions") unless the production code clearly demands them and the production code is available.

When production code is unavailable, grade observable issues in the test body normally, but do not infer missing behaviors or deduct for them. State Production-dependent behavior coverage: Unverified once in the summary so the reader can distinguish test-body findings from claims that require source code.

#### Three sub-dimensions

Compute three sub-grades (each A–F) that together drive the overall grade.

##### A. Assertion strength

Read the loaded language extension's assertion API list and classify every assertion in the test body. Score from highest to lowest:

| Sub-grade | Pattern | |-----------|---------| | A | Pins a richer contract through structural equality or nonredundant related observables (such as returned values plus required state/interaction checks), or qualifies for the exception/error calibration below. Grade predicate depth, not assertion count. Mock-call verifications and bare language assertions count normally. | | B | One clear meaningful assertion that verifies the behavior under test. | | C | Only trivial assertions (single IsNotNull / toBeDefined / assert x is not None), or assertions that leave a meaningful part of the test's claimed result unchecked. A focused single-field claim does not require unrelated fields. | | D | One self-referential / tautological assertion (Assert.AreEqual(x, x), assert dto.name == dto.name, round-trip identity without a non-trivial input), or broad exception assertions (Assert.ThrowsException<Exception>). | | F | No assertions at all; all assertions are always-true literals (Assert.IsTrue(true), assert True, expect(true).toBe(true)) — these verify nothing and are equivalent to having no assertions; or all assertions are silently un-awaited (e.g., expect(promise).resolves.toBe(x) without await/return, async TUnit/xUnit Assert.ThrowsAsync without await, pytest-asyncio with un-awaited coroutine). |

Exception and error-path tests (Assert.ThrowsException<T>, constrained pytest.raises, expect(fn).toThrow, assertThrows, #[should_panic], Should -Throw, EXPECT_THROW, or Go code that verifies an expected non-nil error) are complete on their own. Give Assertion strength A when the test checks the exact promised error condition for its stated scope. Do not deduct for having only that assertion, and do not require an error-message assertion unless the message is part of the documented contract. A Go happy-path test that only checks err == nil while discarding a meaningful returned value is still C because it does not verify the successful result.

##### B. Structure & focus

| Sub-grade | Pattern | |-----------|---------| | A | Clear Arrange-Act-Assert (or Given-When-Then) separation. Single behavior under test. Body under ~30 lines. Setup uses framework conventions. | | B | One mild structural issue (slightly long body, missing blank lines between phases) but intent is clear. | | C | Multiple behaviors mixed in one test, or AAA phases interleaved enough to slow comprehension. | | D | Conditional logic in the test (if/switch driving assertions) — except for idiomatic Go/Rust table-driven sub-test loops; or test relies on previous test state (ordering dependency). | | F | Test exceeds ~60 lines and verifies multiple unrelated behaviors; or shares mutable state with other tests through statics/globals without reset. |

##### C. Anti-pattern hygiene

Scan against the catalog below. The Anti-pattern sub-grade is computed in two passes and combined deterministically:

  1. Hard ceiling pass. Every Critical or High finding sets a

maximum sub-grade (F, D, or C as labeled). Take the worst ceiling across all matched Critical/High findings — these do not accumulate (a single F finding caps the sub-grade at F regardless of how many other Critical/High findings are present).

  1. Medium-deduction pass. Start from A, then for each Medium

finding deduct one sub-grade level (A→B, B→C, C→D, D→F). These do accumulate across findings.

The final Anti-pattern sub-grade is the worse of the two passes (i.e., min(hard_ceiling, A − medium_count)). Low findings never affect the grade — mention them in the note only.

Examples (Critical/High and Medium counts → Anti-pattern sub-grade):

  • Zero Critical/High, 1 Medium → B (A − 1)
  • Zero Critical/High, 3 Medium → D (A − 3)
  • One C-ceiling (e.g., over-mocking), 0 Medium → C
  • One C-ceiling, 2 Medium → C (min(C, A − 2 = C) = C; a third Medium tips to D)
  • One F-finding (e.g., swallowed exception) plus any number of Medium → F

Critical (drop straight to F or D)

  • No assertions at all → F (also drives Assertion sub-grade to F)
  • Swallowed exceptions: try { … } catch { } (.NET), bare except: pass

(Python), try { … } catch (e) {} (JS/TS/Java), defer recover() without re-panic (Go), rescue StandardError with no assertion (Ruby), empty catch (Kotlin/Swift) → F

  • Assert-in-catch pattern (Assert.Fail(ex.Message) instead of

Assert.ThrowsException) → D

  • Always-true literal assertions (Assert.IsTrue(true), assert True,

expect(true).toBe(true)) → F (verifies nothing; also drives Assertion sub-grade to F)

  • Self-referential / tautological assertions on bound values

(Assert.AreEqual(x, x), assert dto.name == dto.name) → D

  • Commented-out assertions → D

High (drop one or two sub-grades)

  • Wall-clock sleep used for synchronization: Thread.Sleep, Task.Delay,

time.sleep, setTimeout-based wait, Thread.sleep, time.Sleep, sleep, std::thread::sleep, Start-Sleep, std::this_thread::sleep_for (in a unit test) → D

  • Unseeded randomness, wall-clock reads without abstraction

(DateTime.Now, datetime.now(), Date.now(), System.currentTimeMillis(), time.Now(), Time.now, Instant::now(), Get-Date, system_clock::now) → D

  • Hard-coded environment-dependent paths (C:\…, /tmp/…, network hosts) → D
  • Ordering dependency on mutable static / package globals → D
  • Broad exception assertion (Assert.ThrowsException<Exception>,

pytest.raises(Exception), expect(fn).toThrow(Error) without matcher, #[should_panic] without expected = "…", Should -Throw without -ExpectedMessage, EXPECT_ANY_THROW) → C

  • Over-mocking: more mock setup lines than test logic, or verifying exact

call sequences instead of outcomes → C

  • Implementation coupling: reflection on private members, casting to

internal types to access state → C

Medium (drop one sub-grade)

  • Poor name: Test1, TestMethod, test, single-word name that says

nothing about scenario or expected outcome (judge against the language extension's convention) → drop one sub-grade

  • Magic values: unexplained 42, "foo", 0x1234 in arrange/assert

without naming or comment → drop one sub-grade

  • Giant test (>30 lines covering a single behavior) → drop one sub-grade
  • Assertion messages that just repeat the assertion text → drop one sub-grade
  • Missing AAA / GWT separation when the test is non-trivial → drop one sub-grade

Low (note only, no deduction)

  • Unused setup/teardown hooks; print debugging left in (Console.WriteLine,

print, console.log, System.out.println, fmt.Println, puts, dbg!, Write-Host, std::cout); inconsistent naming versus siblings; leftover TODO comments. Mention in the note column but do not deduct.

#### Combining sub-grades

Convert sub-grades to numeric points: A=4, B=3, C=2, D=1, F=0.

  • Overall score band = weighted average:

0.45 × Assertion + 0.30 × Anti-pattern + 0.25 × Structure

  • Map to letter:

- ≥ 3.5 → A (band 90–100) - ≥ 2.8 → B (band 80–89) - ≥ 2.0 → C (band 70–79) - ≥ 1.2 → D (band 60–69) - < 1.2 → F (band 0–59)

  • The overall grade is capped at the worst sub-grade — if any sub-grade

is F, the overall grade is F; if the worst sub-grade is D, the overall grade is at most D; and so on. A test that fails on any one dimension cannot earn a higher overall grade than that dimension.

Apply the cap after the weighted mapping, not instead of it. For example, Assertion B, Anti-pattern A, Structure A gives 3.55: the provisional weighted grade is A, but the final grade is B (80-89). A focused scalar equality can therefore be B / Pass with no actionable finding; do not invent a weakness or missing sibling behavior to justify that grade.

Report the letter grade and the score band (not a single 0–100 number). False precision invites bikeshedding; bands keep the conversation focused on the rubric.

Step 4: Assign the decision result

The grade summarizes strength; the result says whether follow-up exists. An actionable improvement is an evidence-backed change to the test, setup, or fixtures. Assign exactly one:

  • Pass — no actionable improvement; positive/context-only notes are allowed.
  • Failed — at least one actionable improvement, regardless of grade.
  • Uncertain — missing evidence prevents a decision and needs human review.
  • Not applicable — a valid scope contains no eligible tests; normally an

overall result with no rows.

Do not derive status from grade: a complete focused test can be B / Pass, while debug output can make an otherwise excellent test A / Failed. Use Uncertain for an unresolved body, unsupported construct, or essential missing contract—not merely absent production code. A definite finding wins over uncertainty.

Step 5: Build the note

Use one sentence (target ≤ 120 characters) for the most important reason: No issues found., Only checks IsNotNull; receipt contents are unverified., or Method body could not be resolved; human review is required. Do not invent a weakness to justify a grade or Failed result.

Keep the action in a separate How to improve field. For each Failed test, name the smallest useful input, assertion, or fixture change and its expected outcome, grounded in the body, source, or an explicit contract. For example, Replace self-comparison with Assert.AreEqual(60m, account.Balance)., not Improve assertions; Remove Console.WriteLine after Deposit(25m)., not Clean up. Prioritize the highest-impact distinct finding, and include other actionable findings only when they require a different change.

For a behavioral gap, use the distinguishing witness and original/mutant observations from the shared assessment; check the expected result against the unmodified source. If essential context is missing, name the evidence needed instead of inventing an expected value. Pass gets None; Uncertain gets a concrete evidence-resolution step, not a speculative test rewrite. A rubric-only deduction is not proof of a behavioral gap or an actionable improvement: a focused B / Pass may need no change. A / Failed still needs its concrete action, such as removing debug output.

Step 6: Report

Produce two sections.

#### 1. Summary

Begin with **Result: <Pass|Failed|Uncertain|Not applicable>**, then give result counts and the highest-priority action. Aggregate using Failed → Uncertain → Pass → Not applicable. For Not applicable, explain the empty scope and omit the table.

#### 2. Per-test table

| Test | Result | Quality | Notes | How to improve |
|------|--------|---------|-------|----------------|
| `Namespace.ClassName.Test_Method_Condition_Expected` | Pass | B (80–89) | One complete value assertion. | None |
| `Namespace.ClassName.Withdraw_SufficientFunds` | Failed | D (60–69) | Balance is compared with itself. | Replace self-comparison with `Assert.AreEqual(60m, account.Balance)` after withdrawing 40m from 100m. |
| `Namespace.ClassName.Test_Missing` | Uncertain | — | Method body could not be resolved; human review is required. | Supply the method body and its referenced fixture. |

Keep these two report sections and the original Test/Result/Quality/Notes fields. When mutation evidence explains a finding or the caller requests detail, append a compact per-test Pseudo-mutation evidence block inside the per-test section: change, witness, original/mutant observations, relevant assertion, and classification. Static results are Likely killed (inferred) or Candidate survivor (unverified), never executed Killed/Survived or empirical killed/total counts. State missing-context N/A / unverified once per shared limitation. Do not repeat the improvement table in prose.

Caps and ordering:

  • If the table would exceed 50 rows, show Failed tests first, then

Uncertain tests, then a sample of Pass tests. Wrap overflow in a collapsed <details> block.

  • Within the same result, order by quality from worst to best, then by file

path and method name for determinism.

  • If the diff context is provided, prefix each test name with a (new) or

(modified) marker.

If multiple languages are present, produce one table per language and prefix each section with the language name and framework.

Related skills

Testing Testing 1,342 tokens

Use the open-source free `coverlet` toolchain for .NET code coverage.

coverlet.collectorcoverlet.msbuild
Testing Testing 1,374 tokens

Write, run, or repair .NET tests that use MSTest.

MSTestMSTest.TestFrameworkMSTest.TestAdapter
Testing Testing 2,713 tokens

Write, run, or repair .NET tests that use NUnit.

NUnitNUnit3TestAdapter

Building AI agents on .NET?

Managed Code builds production AI agents in C# and .NET.