其他
当需要评估现有测试断言是否浅薄、同义反复…
分析现有测试代码中的断言质量、深度、多样性和假信心,适用于 MSTest、Jest、pytest、Go 等测试套件并产出度量报告。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 56/100 ×0.15
适用场景
当需要评估现有测试断言是否浅薄、同义反复通用
解决思路
- 识别目标代码库的语言和测试框架。
- 调用 `test-analysis-extensions` 技能,发现可用的扩展文件。
- 读取与目标代码库语言/框架匹配的扩展文件,例如 `extensions/dotnet.md` 用于 .NET、`extensions/python.md` 用于 pytest、`extensions/typescript.md` 用于 Jest/Vitest、`extensions/go.md` 用于 Go。
- 在归类断言前必须先读取相关扩展文件,因为不同框架的断言 API 差异显著。
- 读取用户提供的所有测试文件。
- 如果用户指向目录或项目,使用语言扩展文件中的标记扫描所有测试文件,例如 MSTest 的 `[TestMethod]`、pytest 的 `def test_*`、Jest 的 `it()` / `test()`、Go 的 `func TestXxx`。
- 对每个测试方法,识别所有断言。
- 将每个断言分类到 Equality:验证返回值匹配预期,例如 `Assert.AreEqual`(MSTest)、`Assert.Equal`(xUnit)、`assert x == y`(pytest)、`expect(x).toBe(y)`(Jest)、`assertEquals`(JUnit)、`if got != want { t.Error... }` / `assert.Equal(t, want, got)`(Go)、`x shouldBe y`(Kotest)、`Should -Be`(Pester)、`EXPECT_EQ`(GoogleTest)。
- 将每个断言分类到 Boolean:验证条件成立,例如 `Assert.IsTrue`、`assert flag`(Python)、`expect(x).toBeTruthy()`(Jest)、`assertTrue`(JUnit)、`assert.True(t, ok)`(testify)、`x.shouldBeTrue()`(Kotest)、`Should -BeTrue`(Pester)、`EXPECT_TRUE`。
- 将每个断言分类到 Null / None / Nil:验证值存在/缺失,例如 `Assert.IsNull`(.NET)、`assert x is None`(pytest)、`expect(x).toBeNull()`(Jest)、`assertNull`(JUnit)、`assert.Nil(t, v)`(testify)、`XCTAssertNil`(XCTest)、`Should -BeNullOrEmpty`(Pester)。
- 将每个断言分类到 Exception / Error:验证错误处理行为,例如 `Assert.Throws<T>()`、`pytest.raises(E)`、`expect(fn).toThrow(E)`、`assertThrows<E>`、`assert.Error(t, err)` / `assert.ErrorIs`、`#[should_panic]`(Rust)、`XCTAssertThrowsError`、`Should -Throw`、`EXPECT_THROW`。
- 将每个断言分类到 Type checks:验证运行时类型正确,例如 `Assert.IsInstanceOfType`、`assert isinstance(x, T)`、`expect(x).toBeInstanceOf(T)`、`assertInstanceOf`、`assert.IsType(t, T{}, v)`、`assert!(matches!(value, Pattern))`(Rust)、`Should -BeOfType`。
- 将每个断言分类到 String:验证文本内容和格式,例如 `StringAssert.Contains`、`assert sub in s`、`expect(s).toMatch(/x/)`、`assertTrue(s.contains(...))`、`assert.Contains(t, s, sub)`、`s shouldContain sub`、`Should -Match`、`EXPECT_THAT(s, HasSubstr(...))`。
- 将每个断言分类到 Collection:验证集合内容和结构,例如 `CollectionAssert.Contains`、`assert item in collection`、`expect(arr).toContain(x)`、`assertIterableEquals`、`assert.Contains(t, slice, item)`、`col shouldContainExactly listOf(...)`、`Should -Contain`、`EXPECT_THAT(c, ElementsAre(...))`。
- 将每个断言分类到 Comparison:验证顺序和大小,例如 `Assert.IsTrue(x > y)`、`Is.GreaterThan`、`assert x > y`、`expect(x).toBeGreaterThan(y)`、`assertTrue(x > y)`、`assert.Greater(t, x, y)`(testify)。
- 将每个断言分类到 Approximate:验证浮点或容差,例如 `Assert.AreEqual(expected, actual, delta)`、`pytest.approx(y)`、`expect(x).toBeCloseTo(y)`、`assertEquals(x, y, delta)`、`assert.InDelta(t, x, y, delta)`、`EXPECT_NEAR`、`EXPECT_DOUBLE_EQ`。
- 将每个断言分类到 Negative:验证不应发生什么,例如 `Assert.AreNotEqual`、`assert x != y`、`expect(x).not.toBe(y)`、`assertNotEquals`、`assert.NotEqual(t, x, y)`、`refute`(Minitest / Ruby)、`Should -Not -Be`。
- 将每个断言分类到 State / Side-effect:验证状态转换和副作用,例如 mutation 后对象属性断言;mock 调用验证 `mock.Verify(...)`(Moq)、`mock_method.assert_called_with(...)`(Python `unittest.mock`)、`expect(mock).toHaveBeenCalledWith(...)`(Jest)、`verify(mock).method(...)`(Mockito)、`Should -Invoke`(Pester)、`expect { code }.to change(obj, :attr)`(RSpec)。
- 将每个断言分类到 Structural / Deep:验证深层对象正确性,例如 `Assert.AreEqual` 与 rich-equality 类型、`assertThat(obj).usingRecursiveComparison()`(AssertJ)、`.toEqual({...})`(Jest deep equality)、`cmp.Diff`(Go go-cmp)、快照测试 `.toMatchSnapshot()` / `syrupy` / `SnapshotTesting`、`assertThat(col).extracting(...)`(AssertJ chains)。
- 一个断言可属于多个类别,例如 `Assert.AreNotEqual` 同时是 Equality 和 Negative;`expect(mock).toHaveBeenCalledWith(...)` 同时是 State/Side-effect 和 specific-call 断言。
- 读取已加载的语言扩展文件,以获取框架特有的准确断言 API 列表。
- 计算每个测试的指标:Assertion count,即每个测试方法中的断言数量。
- 计算每个测试的指标:Assertion categories,即每个测试使用哪些类别。
- 计算套件级指标:Average assertions per test,即总断言数 / 总测试方法数。
- 计算套件级指标:Assertion type spread,即整个套件使用的不同断言类别数,满分 12。
- 计算套件级指标:Tests with zero assertions,即零断言测试方法数量和百分比。
- 计算套件级指标:Tests with only trivial assertions,即所有断言仅为 null check 或 `Assert.IsTrue(true)` 的测试数量和百分比;trivial 表示没有有意义的值验证。
- 计算套件级指标:Tests with self-referential assertions,即断言将输入与自身 round-trip 或 identity-transformed 版本比较,例如 `Assert.AreEqual(input, Parse(input.ToString()))`,或字段与自身比较 `Assert.AreEqual(dto.Name, dto.Name)`;这是同义反复。
- 计算套件级指标:Tests with negative assertions,即数量和百分比;目标:至少 10% 测试应验证不应发生什么。
- 计算套件级指标:Tests with exception assertions,即数量和百分比。
- 计算套件级指标:Tests with state/side-effect assertions,即数量和百分比。
- 计算套件级指标:Tests with structural/deep assertions,即数量和百分比。
- 计算套件级指标:Single-category tests,即只使用一个断言类别的测试数量和百分比。
- 应用校准规则:在描述断言弱点前评估 matcher predicate;对每个弱断言,给出一个现实的缺陷值或行为,它仍会满足该 predicate;对每个被认为有意义的断言,指出它固定住的行为;如果无法从测试和可用生产契约中给出反例,不要推测该断言弱。
- 应用校准规则:trivial 要真正 trivial;单独的 null/None/nil 检查是 trivial(`Assert.IsNotNull(result)`、`assert result is not None`、`expect(x).toBeDefined()`);但 null 检查后跟有意义的值断言不是 trivial——null 检查是真实断言前的 guard;只有测试没有任何有意义的值断言时才标记为 trivial。
- 应用校准规则:使用精确的 Jest 语义;`toBeDefined()` 只拒绝 `undefined`;`null` 会满足它,但只在 `null` 是现实契约破坏结果时提及;`toMatchObject(expected)` 验证期望子集的结构,既不证明对象身份也不证明完整对象相等;绝不要声称它证明了这些。
- 应用校准规则:检查有意义条件的 Boolean 断言不是 trivial;`Assert.IsTrue(result.IsValid)` / `assert result.is_valid` / `expect(result.isValid).toBe(true)` 检查具体属性,是 Boolean 断言而非 trivial;始终为真的断言(`Assert.IsTrue(true)`、`assert True`、`expect(true).toBe(true)`)才是 trivial。
- 应用校准规则:精确构造和映射检查是有意义的;测试构造对象并固定每个请求属性到独立预期字面量,可以捕获交换、丢失或错误赋值;不要因为实现是 constructor、record、property mapping 或 in-memory store 就降级它。
- 应用校准规则:考虑测试意图;测试 void 方法并验证依赖上的状态变化,即使只有一个 Boolean 断言也是合法的。
- 应用校准规则:异常测试天然断言数量低;`Assert.ThrowsException<T>(() => ...)` / `with pytest.raises(E): ...` / `expect(fn).toThrow(E)` / `#[should_panic]` 可能只有这一个断言——对异常聚焦测试没问题;不要因断言数量低而惩罚它们。
- 应用校准规则:mock 调用验证和裸断言形式计入;把 `verify(mock).method(...)`、`expect(mock).toHaveBeenCalledWith(...)`、`Should -Invoke`、bare `assert`、`if got != want { t.Errorf(...) }` 都视为适当类别的真实断言;不要把它们当作 missing-framework-API smell。
- 应用校准规则:快照断言(`.toMatchSnapshot()`、`syrupy`、`SnapshotTesting`)计为 Structural/Deep 断言;单独标记 stale 或 never-updated 快照。
- 应用校准规则:property-based tests(`@given` Hypothesis、`proptest!`、`forAll` Kotest)通过生成用例隐式生成断言——计算内部断言逻辑,不计算外层脚手架。
- 应用校准规则:不要把 diversity 和 volume 混为一谈;20 个 equality 断言的测试 volume 高但 diversity 低;一个 equality、一个 null check、一个 exception 断言的测试 volume 低但 diversity 好。
- 应用校准规则:self-referential 断言不是有意义的 equality 检查;断言输出等于输入 round-trip 看起来像真实 equality 断言,但当被测操作预期是 identity 时是同义反复;单独标记这些;如果测试目的是验证 round-trip(序列化/反序列化、编码/解码),断言有效,但应同时包含针对非平凡输入的断言以触发转换。
- 应用校准规则:让建议匹配命名行为;格式化测试应固定精确格式化表示;验证测试需要被拒绝输入;round-trip 测试需要触发 escaping、null/empty 处理或其他转换边界的输入;对每个零断言的 create/update/delete 操作,推荐其特定返回值或可观察后置条件,而不是通用的“检查状态”补救。
- 应用校准规则:如果断言已经良好多样化,就直接说明;报告结论为套件有多样性是完全可以的。
- 报告发现:根据套件规模和复杂度缩放报告深度;对 substantial suite(大约 15+ 测试或多文件项目)使用完整模板;对小而简单的输入(单文件少量测试)不要输出每个 section——直接简洁回答用户问题:哪些测试 assertion-free 或 trivial-only、总体断言质量结论、具体建议(仍区分有意 smoke tests 与伪装成真实验证的测试);只使用对当前输入有真实信号的 section;简短 metric summary 加 assertion-free 列表和建议通常足够;绝不省略 rubric 相关实质(assertion-free/trivial 识别、质量结论、具体建议),只裁剪无信息的结构开销。
- 报告发现:对五到八个测试的文件,默认给一个 verdict 加一个紧凑 per-test table;除非调用者要求 metrics,省略 category-spread dashboards 和 hypothetical failure modes;只陈述由断言 predicate 和可用生产行为支持的反例。
- 报告发现:按以下结构呈现分析:1. Summary Dashboard——关键指标快速参考表,包含 Total tests、Average assertions per test、Assertion type spread、Tests with zero assertions、Tests with only trivial asserts、Tests with negative assertions、Single-category tests 等,并附 Assessment。
- 报告发现:2. Category Breakdown——对每个断言类别展示:多少测试使用它、代码中的代表性示例、相对于被测代码是过度使用还是使用不足。
- 报告发现:3. Gap Analysis——基于生产代码(如果可用)识别:只测试了 equality checks 的行为、没有 exception assertions 的错误路径、没有 state verification 的状态改变方法、返回了 collection 但从未检查内容。
- 报告发现:4. Recommendations——优先改进列表:哪些测试最受益于额外断言类型、缺少哪些断言类别及为何重要、可以添加的断言具体示例。
- 报告发现:5. Assertion-free tests——如果存在,列出每个测试的方法名和它看起来在测试什么,以便用户决定添加断言或标记为有意 smoke tests。
- 验证:每个断言都被分类到至少一个类别。
- 验证:指标计算正确,计数相加无误。
- 验证:trivial-assertion 测试被正确识别,不要过度标记。
- 验证:exception 测试不因低断言数量被惩罚。
- 验证:有意义属性上的 Boolean 断言不被分类为 trivial。
- 验证:每个 weak-assertion 声明包含一个现实反例,该反例会被精确 matcher 接受。
- 验证:Jest matcher 语义精确(`toBeDefined` 与 `undefined`;`toMatchObject` 子集匹配与 identity/full equality)。
- 验证:建议具体,点名特定测试方法并建议特定断言类型。
- 验证:如果套件有多样性,报告承认这一点。
- 常见陷阱:不要因异常测试断言数量低而惩罚;异常断言本身是完整的——跳过这些的 count warnings。
- 常见陷阱:不要把值检查前的 null/None/nil 检查标记为 trivial;只标记 null/None/nil 检查是唯一断言的测试。
- 常见陷阱:不要把所有 Boolean 断言都算作 trivial;只有始终为真的断言(`Assert.IsTrue(true)`、`assert True`、`expect(true).toBe(true)`)才是 trivial。
- 常见陷阱:不要忽略框架差异;每个框架有不同断言 API——先读取匹配的语言扩展;MSTest 的 `Assert.AreEqual`、xUnit 的 `Assert.Equal`、NUnit 的 `Is.EqualTo`、pytest 的裸 `assert ==`、Jest 的 `expect().toBe()`、Go 的 `if … { t.Error… }` 都映射到 Equality 类别。
- 常见陷阱:不要把裸断言形式当作 missing-framework;pytest 的裸 `assert`、Go 的 `if got != want { t.Error... }`、Rust 的 `assert!()` 是规范形式——计入正确类别。
- 常见陷阱:不要把 mock 调用验证当作 assertion-free;`verify(mock).method(...)`、`expect(mock).toHaveBeenCalledWith(...)`、`Should -Invoke` 是 State/Side-effect 断言。
- 常见陷阱:不要为 diversity 而 diversity;只建议添加能捕获被测代码真实 bug 的断言类型。
- 常见陷阱:不要遗漏隐式断言;exception 断言同时是 Exception 和 Negative;snapshot/property-based 测试是带隐式结构的真实断言。
- 常见陷阱:处理 async 测试中未 await 的断言;TUnit、Jest 的 `.resolves`/`.rejects`、pytest-asyncio、Swift Testing、Kotest 都会在断言未 `await` 时静默通过——即使存在断言调用,也视为 assertion-free。
适用边界 · 注意事项
- 用户要编写新测试时,应使用 `code-testing-agent` 或针对 MSTest 的 `writing-mstest-tests`,不要用本技能。
- 用户要检测断言之外的通用反模式时,应使用 `test-anti-patterns`,不要用本技能。
- 用户要修复/重写断言,或只关心代码覆盖率百分比时,不要用本技能。