grade-tests

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Grade Tests

测试评分

Grade a curated list of test methods and produce a compact, PR-comment-friendly report: one row per test method with a letter grade, a score band, and a one-line note explaining the grade. The skill does not discover tests on its own — the caller (typically a PR automation workflow or a human reviewer holding a specific list) provides the test methods to grade.
Language-specific guidance: Call the
test-analysis-extensions
skill to discover available extension files, then read the file matching the target codebase's language and framework (e.g.,
extensions/dotnet.md
,
extensions/python.md
,
extensions/typescript.md
,
extensions/go.md
). You MUST read the relevant extension file before scoring assertions or anti-patterns, because assertion APIs and idiomatic patterns differ significantly across frameworks.
对精选的测试方法列表进行评分,生成紧凑、适合作为PR评论的报告:每个测试方法对应一行,包含字母等级、分数区间和一行说明评分理由的文字。本skill不会自行发现测试——调用方(通常是PR自动化工作流或持有特定列表的人工审核者)需提供待评分的测试方法。
语言特定指南:调用
test-analysis-extensions
skill以发现可用的扩展文件,然后读取与目标代码库语言和框架匹配的文件(例如
extensions/dotnet.md
extensions/python.md
extensions/typescript.md
extensions/go.md
)。在对断言或反模式评分前,你必须阅读相关扩展文件,因为不同框架的断言API和惯用模式差异显著。

Why a Per-Test Grade

为何采用逐测试评分

Suite-wide audits (
test-anti-patterns
,
assertion-quality
,
test-smell-detection
) produce excellent diagnostic reports, but they are hard to consume as a short PR comment. Reviewers of a PR mostly want to know: for the tests this PR adds or changes, are they good? This skill answers that question with a one-row-per-test verdict that fits in a comment table.
全套件审计(
test-anti-patterns
assertion-quality
test-smell-detection
)会生成出色的诊断报告,但作为简短的PR评论却难以阅读。PR审核者最想知道的是:*本次PR新增或修改的测试质量如何?*本skill通过每行对应一个测试的结论来回答这个问题,结果可直接放入评论表格中。

When to Use

适用场景

  • A PR automation workflow needs to post a comment grading the tests introduced or modified in a pull request.
  • A reviewer has a specific list of tests (a file, a class, a method list, or a diff hunk) and wants a per-test verdict rather than a suite report.
  • A maintainer wants to triage which of N tests in a contribution deserve follow-up improvements.
  • PR自动化工作流需要发布评论,对拉取请求中引入或修改的测试进行评分。
  • 审核者持有特定测试列表(文件、类、方法列表或代码片段),希望获取逐测试结论而非全套件报告。
  • 维护者希望筛选出贡献中的N个测试,确定哪些需要后续改进。

When Not to Use

不适用场景

  • The caller wants a full suite audit or comparative metrics — use
    test-anti-patterns
    (pragmatic) or
    test-smell-detection
    (formal) and let the
    test-quality-auditor
    agent orchestrate.
  • The caller wants to write new tests — use
    code-testing-generator
    (any language) or
    writing-mstest-tests
    (MSTest specifically).
  • The caller wants to measure code coverage or CRAP scores — use
    coverage-analysis
    or
    crap-score
    (.NET only).
  • The caller wants to fix issues directly in test code — invoke the appropriate editing skill.
  • No specific list of tests is provided. Do not try to grade every test in the workspace; ask the caller for an explicit list or scope.
  • 调用方需要全套件审计或对比指标——使用
    test-anti-patterns
    (实用型)或
    test-smell-detection
    (规范型),并让
    test-quality-auditor
    Agent进行协调。
  • 调用方想要编写新测试——使用
    code-testing-generator
    (支持任意语言)或
    writing-mstest-tests
    (专为MSTest设计)。
  • 调用方想要衡量代码覆盖率或CRAP分数——使用
    coverage-analysis
    crap-score
    (仅支持.NET)。
  • 调用方想要直接修复测试代码中的问题——调用相应的编辑skill。
  • 未提供特定测试列表。请勿尝试对工作区中的所有测试进行评分;请要求调用方提供明确的列表或范围。

Inputs

输入参数

InputRequiredDescription
Test methodsYesA scope to grade. Provide one of: (a) an explicit list of test method names (fully-qualified, e.g.
Namespace.ClassName.TestMethodName
); (b) one or more file paths plus an explicit instruction to grade every test declared in those files; or (c) a diff hunk / PR identifier whose changed tests should be graded. File paths are recommended but optional when method names are unambiguous in the workspace. Ambiguous requests like "grade my tests" with no scope are rejected up-front (see Step 0); this skill is for curated input and does not auto-grade an entire workspace.
Test bodies / spansRecommendedThe exact source lines for each test method. If omitted, read them from the listed files.
Production codeNoThe code under test, for judging whether assertions cover the meaningful behaviors. When unavailable, mark relevant findings as "Unverified" rather than guessing.
Diff contextNoWhen grading PR changes, the unified diff for each test method helps focus on what actually changed.
输入项是否必填描述
测试方法待评分的范围。提供以下其中一项:(a)明确的测试方法名称列表(完全限定,例如
Namespace.ClassName.TestMethodName
);(b)一个或多个文件路径,加上明确的指令要求对这些文件中声明的所有测试进行评分;或(c)代码片段/PR标识符,需对其中修改的测试进行评分。当方法名称在工作区中无歧义时,推荐但可选提供文件路径。模糊的请求(如*“为我的测试评分”*)将直接被拒绝(见步骤0);本skill仅适用于精选输入,不会自动对整个工作区进行评分。
测试方法体/范围推荐每个测试方法的精确源代码行。若未提供,则从列出的文件中读取。
生产代码被测代码,用于判断断言是否覆盖了有意义的行为。若不可用,将相关发现标记为“未验证”,而非猜测。
代码差异上下文对PR变更进行评分时,每个测试方法的统一差异有助于聚焦实际变更内容。

Step 0: Validate the input

步骤0:验证输入

Before doing anything else, check that the caller provided one of:
  1. An explicit list of test method names, or
  2. One or more file paths plus an explicit instruction to grade every test declared in those files (e.g., "grade every test in
    OrderTests.cs
    "), or
  3. A diff hunk or PR identifier whose changed tests should be graded.
If the request is ambiguous (e.g., "Grade my tests", "Are these tests any good?" with no scope, "Review the test suite"), do not load extensions, do not read files, and do not grade anything. Reply with a short message asking the caller to provide an explicit list / file(s) / diff, and optionally point them at
test-quality-auditor
agent or
test-anti-patterns
skill for full-suite analysis. Stop there.
在执行任何操作前,检查调用方是否提供了以下其中一项:
  1. 明确的测试方法名称列表,
  2. 一个或多个文件路径,加上明确的指令要求对这些文件中声明的所有测试进行评分(例如“为
    OrderTests.cs
    中的所有测试评分”),
  3. 代码片段或PR标识符,需对其中修改的测试进行评分。
如果请求模糊不清(例如:“为我的测试评分”“这些测试好不好?”但未指定范围、“审核测试套件”),请勿加载扩展、读取文件或进行任何评分。回复简短消息,要求调用方提供明确的列表/文件/差异,并可选择性地引导他们使用
test-quality-auditor
Agent或
test-anti-patterns
skill进行全套件分析。操作到此为止。

Workflow

工作流程

Step 1: Detect language and load extension

步骤1:检测语言并加载扩展

Identify the target codebase's language and test framework from the file extensions and the test method markers in the provided list. Call the
test-analysis-extensions
skill and read the matching extension file (e.g.,
extensions/dotnet.md
for MSTest/xUnit/NUnit/TUnit,
extensions/python.md
for pytest,
extensions/typescript.md
for Jest/Vitest,
extensions/go.md
for the standard
testing
package). If the input contains tests from multiple languages, load each relevant extension and grade each test using its language's conventions.
从文件扩展名和提供列表中的测试方法标记,识别目标代码库的语言和测试框架。调用
test-analysis-extensions
skill并读取匹配的扩展文件(例如,针对MSTest/xUnit/NUnit/TUnit的
extensions/dotnet.md
,针对pytest的
extensions/python.md
,针对Jest/Vitest的
extensions/typescript.md
,针对标准
testing
包的
extensions/go.md
)。如果输入包含多种语言的测试,请加载每个相关扩展,并使用对应语言的约定对每个测试进行评分。

Step 2: Resolve the test bodies

步骤2:解析测试方法体

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.
  3. If a method cannot be found, record it as
    N/A — method not found
    and continue. Never invent a body to grade.
针对输入列表中的每个条目:
  1. 如果测试方法体已内联提供,则直接使用。
  2. 否则,读取指定路径的文件,并通过完全限定名称定位方法。捕获完整的方法体,包括属性/装饰器/夹具以及测试调用的任何辅助代码。
  3. 如果无法找到方法,记录为
    N/A — 方法未找到
    并继续。切勿虚构方法体进行评分。

Step 3: Score each test

步骤3:对每个测试评分

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.
每个测试初始等级为A(分数区间90–100),然后仅针对捕获的方法体中可观察到的问题进行扣分。请勿因假设性问题(例如“本可添加更多负面断言”)扣分,除非生产代码明确需要且生产代码可用。
当生产代码不可用时,正常对测试方法体中可观察到的问题进行评分,但不要推断缺失的行为或因此扣分。在摘要中注明一次
生产相关行为覆盖率:未验证
,以便读者区分测试方法体的发现与需要源代码支撑的结论。

Three sub-dimensions

三个子维度

Compute three sub-grades (each A–F) that together drive the overall grade.
计算三个子等级(均为A–F),共同决定最终等级。
A. Assertion strength
A. 断言强度
Read the loaded language extension's assertion API list and classify every assertion in the test body. Score from highest to lowest:
Sub-gradePattern
AAt least one meaningful value assertion (equality / structural / exception / state) plus, where appropriate, additional checks (negative, type, collection contents). Mock-call verifications (
Verify
,
toHaveBeenCalledWith
,
Should -Invoke
) and bare assertion forms (pytest
assert
, Go
if got != want { t.Errorf(...) }
, Rust
assert!()
) count as real assertions.
BOne clear meaningful assertion that verifies the behavior under test.
COnly trivial assertions (single
IsNotNull
/
toBeDefined
/
assert x is not None
), or assertions that check a single field while the operation produces a richer result.
DOne 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>
).
FNo 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.
读取已加载语言扩展中的断言API列表,对测试方法体中的每个断言进行分类。评分从高到低如下:
子等级模式
A至少包含一个有意义的值断言(相等/结构/异常/状态),并在适当情况下包含额外检查(负面、类型、集合内容)。模拟调用验证(
Verify
toHaveBeenCalledWith
Should -Invoke
)和基础断言形式(pytest的
assert
、Go的
if got != want { t.Errorf(...) }
、Rust的
assert!()
)均视为有效断言。
B一个清晰的有意义断言,验证被测行为。
C仅包含琐碎断言(单一
IsNotNull
/
toBeDefined
/
assert x is not None
),或断言仅检查单个字段但操作产生更丰富结果。
D一个自引用/同义反复的断言(
Assert.AreEqual(x, x)
assert dto.name == dto.name
、无重要输入的往返同一性验证),或宽泛的异常断言(
Assert.ThrowsException<Exception>
)。
F无任何断言;所有断言均为恒真字面量(
Assert.IsTrue(true)
assert True
expect(true).toBe(true)
)——这些断言无法验证任何内容,等同于无断言;或所有断言均未被等待(例如
expect(promise).resolves.toBe(x)
未加
await
/
return
、异步TUnit/xUnit的
Assert.ThrowsAsync
未加
await
、pytest-asyncio中未等待的协程)。
异常和错误路径测试(
Assert.ThrowsException<T>
、受限的
pytest.raises
expect(fn).toThrow
assertThrows
#[should_panic]
Should -Throw
EXPECT_THROW
,或验证预期非空错误的Go代码)本身是完整的。当测试检查其声明范围内的确切预期错误条件时,断言强度等级为A。请勿因仅包含该断言而扣分,且除非消息是文档化契约的一部分,否则无需要求错误消息断言。仅检查
err == nil
而丢弃有意义返回值的Go正常路径测试仍为C,因为它未验证成功结果。
B. Structure & focus
B. 结构与聚焦
Sub-gradePattern
AClear Arrange-Act-Assert (or Given-When-Then) separation. Single behavior under test. Body under ~30 lines. Setup uses framework conventions.
BOne mild structural issue (slightly long body, missing blank lines between phases) but intent is clear.
CMultiple behaviors mixed in one test, or AAA phases interleaved enough to slow comprehension.
DConditional 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).
FTest exceeds ~60 lines and verifies multiple unrelated behaviors; or shares mutable state with other tests through statics/globals without reset.
子等级模式
A清晰的Arrange-Act-Assert(或Given-When-Then)分离。单一被测行为。方法体约30行以内。使用框架约定进行初始化。
B存在一个轻微的结构问题(方法体略长、阶段间缺少空行),但意图清晰。
C一个测试中混合了多个行为,或AAA阶段交错严重影响理解。
D测试中包含条件逻辑(
if
/
switch
驱动断言)——除非是Go/Rust中惯用的表格驱动子测试循环;或测试依赖于前一个测试的状态(顺序依赖)。
F测试超过约60行并验证多个无关行为;或通过静态/全局变量与其他测试共享可变状态且未重置。
C. Anti-pattern hygiene
C. 反模式合规性
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).
  2. 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 → D (
    min(C, A − 2 = C) = C
    , but a third Medium would tip 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.
对照以下目录进行扫描。反模式子等级通过两轮计算并确定组合结果:
  1. 硬上限轮次。每个严重高风险发现会设置一个最高子等级(标记为F、D或C)。取所有匹配的严重/高风险发现中最差的上限——这些发现不会累积(单个F发现会将子等级上限设为F,无论其他严重/高风险发现数量多少)。
  2. 中等扣分轮次。从A开始,每个中等发现扣除一个子等级(A→B、B→C、C→D、D→F)。这些发现会累积。
最终反模式子等级为两轮结果中更差的那个(即
min(硬上限, A − 中等发现数量)
)。低风险发现永远不会影响等级——仅在说明中提及。
示例(严重/高风险和中等发现数量 → 反模式子等级):
  • 无严重/高风险,1个中等 → B(A − 1)
  • 无严重/高风险,3个中等 → D(A − 3)
  • 一个C上限(例如过度模拟),0个中等 → C
  • 一个C上限,2个中等 → C
    min(C, A − 2 = C) = C
    ,但第三个中等发现会降至D
  • 一个F发现(例如吞掉异常)加任意数量中等 → F
严重(直接降至F或D)
  • 无任何断言 → F(同样会将断言子等级降至F)
  • 吞掉异常:
    try { … } catch { }
    (.NET)、 bare
    except: pass
    (Python)、
    try { … } catch (e) {}
    (JS/TS/Java)、
    defer recover()
    未重新panic(Go)、
    rescue StandardError
    无断言(Ruby)、空
    catch
    (Kotlin/Swift)→ F
  • 断言在catch中的模式(
    Assert.Fail(ex.Message)
    而非
    Assert.ThrowsException
    )→ D
  • 恒真字面量断言(
    Assert.IsTrue(true)
    assert True
    expect(true).toBe(true)
    )→ F(无法验证任何内容;同样会将断言子等级降至F)
  • 绑定值的自引用/同义反复断言(
    Assert.AreEqual(x, x)
    assert dto.name == dto.name
    )→ D
  • 被注释掉的断言 → D
高风险(降低一到两个子等级)
  • 使用挂钟睡眠进行同步:
    Thread.Sleep
    Task.Delay
    time.sleep
    、基于
    setTimeout
    的等待、
    Thread.sleep
    time.Sleep
    sleep
    std::thread::sleep
    Start-Sleep
    std::this_thread::sleep_for
    (在单元测试中)→ D
  • 未初始化的随机性、无抽象的挂钟读取(
    DateTime.Now
    datetime.now()
    Date.now()
    System.currentTimeMillis()
    time.Now()
    Time.now
    Instant::now()
    Get-Date
    system_clock::now
    )→ D
  • 硬编码依赖环境的路径(
    C:\…
    /tmp/…
    、网络主机)→ D
  • 对可变静态/包全局变量的顺序依赖 → D
  • 宽泛的异常断言(
    Assert.ThrowsException<Exception>
    pytest.raises(Exception)
    expect(fn).toThrow(Error)
    无匹配器、
    #[should_panic]
    expected = "…"
    Should -Throw
    -ExpectedMessage
    EXPECT_ANY_THROW
    )→ C
  • 过度模拟:模拟设置行数多于测试逻辑,或验证精确调用序列而非结果 → C
  • 实现耦合:对私有成员的反射、转换为内部类型以访问状态 → C
中等(降低一个子等级)
  • 命名不佳:
    Test1
    TestMethod
    test
    、未说明场景或预期结果的单字名称(对照语言扩展的约定判断)→ 降低一个子等级
  • 魔法值:在初始化/断言中未命名或未注释的
    42
    "foo"
    0x1234
    → 降低一个子等级
  • 巨型测试(>30行覆盖单一行为)→ 降低一个子等级
  • 断言消息仅重复断言文本 → 降低一个子等级
  • 非 trivial 测试缺少AAA/GWT分离 → 降低一个子等级
低风险(仅说明,不扣分)
  • 未使用的初始化/清理钩子;遗留的打印调试代码(
    Console.WriteLine
    print
    console.log
    System.out.println
    fmt.Println
    puts
    dbg!
    Write-Host
    std::cout
    );与同级测试命名不一致;遗留的TODO注释。在说明列中提及但不扣分。

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.
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.
将子等级转换为数值:A=4、B=3、C=2、D=1、F=0。
  • 总分数区间 = 加权平均值:
    0.45 × 断言强度 + 0.30 × 反模式合规性 + 0.25 × 结构与聚焦
  • 映射为字母等级:
    • ≥ 3.5 → A(区间90–100)
    • ≥ 2.8 → B(区间80–89)
    • ≥ 2.0 → C(区间70–79)
    • ≥ 1.2 → D(区间60–69)
    • < 1.2 → F(区间0–59)
  • 最终等级受最差子等级限制——如果任何子等级为F,最终等级为F;如果最差子等级为D,最终等级最高为D;以此类推。在任一维度不合格的测试,无法获得高于该维度的最终等级。
报告字母等级分数区间(而非0–100的单一数值)。虚假的精度会引发无意义的争论;区间能让讨论聚焦于评分标准。

Step 4: Build the note

步骤4:编写说明

The note column is one short sentence (target ≤ 120 characters). State the single most important reason for the grade. Examples:
  • A (90–100):
    Clear AAA structure; equality + exception assertions on the public contract.
  • B (80–89):
    Good assertion variety, mildly long body — consider splitting into per-condition tests.
  • C (70–79):
    Only checks IsNotNull on the result; no value verification.
  • D (60–69):
    Self-referential assertion: round-trip identity verifies plumbing, not transformation.
  • F (0–59):
    No assertions — test executes the method but never verifies anything.
If a test gets A with no notable issues, the note may simply be
No issues found.
— do not invent weaknesses to justify the grade.
说明列是一句简短的话(目标≤120字符)。说明等级的最重要原因。示例:
  • A (90–100):
    清晰的AAA结构;针对公共契约的相等性+异常断言。
  • B (80–89):
    断言类型丰富,方法体略长——考虑按条件拆分为多个测试。
  • C (70–79):
    仅检查结果的IsNotNull;无值验证。
  • D (60–69):
    自引用断言:往返同一性仅验证管道,而非转换逻辑。
  • F (0–59):
    无断言——测试执行方法但从未验证任何内容。
如果测试获得A且无明显问题,说明可简单写为
未发现问题。
——请勿为了平衡而虚构缺陷。

Step 5: Report

步骤5:生成报告

Produce two sections.
生成两个部分。

1. Summary

1. 摘要

A short paragraph (2–4 sentences) covering: total tests graded, grade distribution, most common issue, and the single most important recommendation.
简短段落(2–4句话),涵盖:评分的测试总数、等级分布、最常见问题,以及最重要的单一建议。

2. Per-test table

2. 逐测试表格

markdown
| Test | Grade | Band | Notes |
|------|-------|------|-------|
| `Namespace.ClassName.Test_Method_Condition_Expected` | A | 90–100 | Clear AAA; equality + exception assertions. |
| `Namespace.ClassName.Test_Other` | C | 70–79 | Only `IsNotNull` — no value verification. |
| `Namespace.ClassName.Test_Old` | F | 0–59 | No assertions. |
Caps and ordering:
  • If the table would exceed 50 rows, show all tests graded below B first (worst to best), then a sample of the best tests, and wrap any overflow in a collapsed
    <details>
    block.
  • Within the same grade, order by file path then by 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.
markdown
| 测试 | 等级 | 区间 | 说明 |
|------|-------|------|-------|
| `Namespace.ClassName.Test_Method_Condition_Expected` | A | 90–100 | 清晰的AAA结构;相等性+异常断言。 |
| `Namespace.ClassName.Test_Other` | C | 70–79 | 仅包含`IsNotNull`——无值验证。 |
| `Namespace.ClassName.Test_Old` | F | 0–59 | 无断言。 |
行数限制与排序:
  • 如果表格行数超过50行,先显示所有等级低于B的测试(从最差到最好),然后显示部分优秀测试的样本,并将溢出部分放入折叠的
    <details>
    块中。
  • 同一等级内,按文件路径然后方法名称排序,确保确定性。
  • 如果提供了代码差异上下文,在每个测试名称前添加
    (新增)
    (修改)
    标记。
如果存在多种语言,为每种语言生成一个表格,并在每个部分前添加语言名称和框架。

Validation

验证清单

  • Every test in the input list appears in the table (or is recorded as
    N/A — method not found
    ).
  • Every grade is justified by at least one observable signal in the captured body — no speculative deductions.
  • Trivial-assertion tests are flagged only when the only assertion is trivial (a null check before a meaningful assertion is not trivial).
  • Exception-only tests are not penalized for low assertion count.
  • Mock-call verifications and bare assertion forms count as real assertions of the appropriate category.
  • Boolean assertions on meaningful properties (
    Assert.IsTrue(result.IsValid)
    ) are not classified as always-true; only literal
    true
    /
    false
    constants are.
  • Self-referential assertions are flagged separately from normal equality assertions.
  • Idiomatic patterns are not flagged: Go/Rust table-driven sub-tests, pytest bare
    assert
    , Go
    if got != want { t.Errorf(...) }
    , JS/TS
    expect(mock).toHaveBeenCalledWith(...)
    .
  • Async test pitfalls (un-awaited
    resolves
    /
    rejects
    /
    ThrowsAsync
    , pytest-asyncio without
    await
    ) drop the Assertion sub-grade to F.
  • The summary leads with the highest-leverage observation, not a recap of the table.
  • 输入列表中的每个测试都出现在表格中(或记录为
    N/A — 方法未找到
    )。
  • 每个等级都至少有一个捕获方法体中的可观察信号作为依据——无推测性扣分。
  • 仅当唯一的断言是琐碎断言时,才标记琐碎断言测试(有意义断言前的空值检查不算琐碎)。
  • 仅包含异常的测试不会因断言数量少而被扣分。
  • 模拟调用验证和基础断言形式被视为对应类别的有效断言。
  • 对有意义属性的布尔断言(
    Assert.IsTrue(result.IsValid)
    )不被归类为恒真;只有字面量
    true
    /
    false
    常量才会被归类。
  • 自引用断言与正常相等断言被分开标记。
  • 惯用模式不会被标记:Go/Rust表格驱动子测试、pytest的bare
    assert
    、Go的
    if got != want { t.Errorf(...) }
    、JS/TS的
    expect(mock).toHaveBeenCalledWith(...)
  • 异步测试陷阱(未等待的
    resolves
    /
    rejects
    /
    ThrowsAsync
    、pytest-asyncio未加
    await
    )会将断言子等级降至F。
  • 摘要以最高价值的观察结果开头,而非表格的复述。

Common Pitfalls

常见陷阱

PitfallSolution
Grading every test in the workspace when no list is providedAsk the caller for the explicit list; this skill is for curated input.
Inflating deductions to justify the gradeStart at A; deduct only for observable issues.
Penalizing exception tests for low assertion countException assertions are complete on their own.
Downgrading a focused Go error-path test because it checks only
err != nil
Expected-error existence is the observable contract for that scope; keep it at A unless the production contract requires a specific error identity or message.
Treating
IsNotNull
before a value assertion as trivial
Only flag when the null check is the only assertion.
Treating any Boolean assertion as effectively assertion-freeOnly always-true literals (
Assert.IsTrue(true)
,
assert True
) are; meaningful
Assert.IsTrue(result.IsValid)
is a real assertion.
Flagging Go/Rust table-driven loops as conditional logicThey are idiomatic; do not deduct.
Treating pytest bare
assert
or Go
if got != want { t.Error… }
as missing-framework
Both are canonical; count in the correct assertion category.
Penalizing tests when production code is unavailableMark concerns about uncovered behaviors as
Unverified
and do not deduct.
Using a fake-precise score (e.g., 87/100)Use the score band only — 90–100, 80–89, 70–79, 60–69, 0–59.
Spilling a 500-row table into a PR commentApply the row cap from Step 5; collapse extras into
<details>
.
Re-reporting an existing finding three times under different categoriesPick the most fitting category and report once.
Inventing weaknesses for A-grade tests to make the note "balanced"If a test is clean, the note may simply read
No issues found.
陷阱解决方案
未提供列表时对工作区中的所有测试进行评分要求调用方提供明确列表;本skill仅适用于精选输入。
为了证明等级合理性而夸大扣分从A开始;仅针对可观察到的问题扣分。
因断言数量少而惩罚仅包含异常的测试异常断言本身是完整的。
因仅检查
err != nil
而降级聚焦的Go错误路径测试
预期错误的存在是该范围内的可观察契约;除非生产契约要求特定错误标识或消息,否则保持等级为A。
将有意义断言前的
IsNotNull
视为琐碎
仅当空值检查是唯一断言时才标记。
将任何布尔断言视为无断言只有恒真字面量(
Assert.IsTrue(true)
assert True
)才会被视为无断言;有意义的
Assert.IsTrue(result.IsValid)
是有效断言。
将Go/Rust表格驱动循环标记为条件逻辑这些是惯用写法;请勿扣分。
将pytest的bare
assert
或Go的
if got != want { t.Error… }
视为缺少框架支持
两者都是标准写法;归入正确的断言类别。
生产代码不可用时惩罚测试将未覆盖行为的担忧标记为
未验证
,不扣分。
使用虚假精确的分数(例如87/100)仅使用分数区间——90–100、80–89、70–79、60–69、0–59。
将500行的表格放入PR评论应用步骤5中的行数限制;将溢出部分折叠到
<details>
中。
在不同类别下重复报告同一现有发现选择最合适的类别并仅报告一次。
为A级测试虚构缺陷以让说明“平衡”如果测试无问题,说明可简单写为
未发现问题。