关于 test-anti-patterns
test-anti-patterns 是用于审查测试代码质量的技能,面向需要诊断测试可靠性、可维护性和诊断价值问题的开发者。它按严重程度检查测试反模式,并支持多种语言与测试框架。
主要功能
- 01检测测试反模式与质量问题
- 02按严重程度整理发现
- 03支持多种语言与测试框架
- 04可聚焦特定质量问题
适用场景
- 01审查测试代码并查找异味
- 02排查测试不稳定或不可靠
- 03执行测试审计并获取诊断结果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill test-anti-patterns安装后在 Claude Code 中输入/test-anti-patterns确认已加载。
详细介绍
测试反模式检测
快速、务实地分析任何受支持语言的测试代码,找出会损害测试可靠性、可维护性和诊断价值的反模式与质量问题。
特定语言指导:需要时,阅读调用方提供或运行时列出的
test-analysis-extensions目录及对应的语言文件。 不要调用仅供参考的 辅助工具。如果这些文件不存在,请使用下方内置的框架规则。
适用场景
- 用户要求检查测试质量或查找测试异味
- 用户想知道测试为何不稳定或不可靠
- 用户询问“我的测试写得好吗?”或“我的测试有什么问题?”
- 用户要求进行测试审计或测试代码审查
- 用户希望先了解诊断结果,再决定要改进什么
不适用场景
- 用户想从头编写新测试(使用
code-testing) - 用户希望直接实现修复,而不是进行诊断审查(使用相关的编写/编辑技能)
- 用户要求修复 MSTest 中顺序颠倒的
Assert.AreEqual参数(使用writing-mstest-tests) - 用户要求将 MSTest
DynamicData从IEnumerable<object[]>转换为ValueTuple(使用writing-mstest-tests) - 用户想运行或执行测试(对于 .NET,使用
run-tests) - 用户想在测试框架或版本之间迁移(使用迁移技能)
- 用户想收集原始 .NET 覆盖率(使用
run-tests)、收集或分析非 .NET 覆盖率(使用原生工具)、分析项目范围的 .NET 覆盖率/CRAP 指标(使用coverage-analysis),或分析指定目标的 .NET CRAP(使用crap-score) - 用户询问测试能否发现某个 Bug,或想了解行为层面/伪变异方面的缺口(使用
test-gap-analysis) - 用户想分类测试组合或正常路径与错误路径、进行标准化标记,或统计特征/类别分布(使用
test-tagging) - 用户想进行深入、正式的测试异味审计,采用学术分类法和扩展目录(使用
test-smell-detection)
输入
| 输入 | 必填 | 说明 |
|---|---|---|
| 测试范围 | 否 | 要分析的测试文件、类、目录或项目。若未提供,则从当前工作区中发现。 |
| 生产代码 | 否 | 被测代码,用于了解测试应验证的内容 |
| 具体关注点 | 否 | 用于缩小审查范围的重点领域,例如“不稳定性”或“命名” |
工作流
第 1 步:检测语言并加载扩展
在询问用户之前,先从当前工作区解析指定的测试路径。
如果未提供路径,则使用仓库清单和惯用的测试标记,在当前目录下发现测试文件。技能上下文中的
Base directory 是文档存储位置,不是用户的工作区;绝不能以此为基准解析目标文件。
如果某个读取器报告路径缺失,但工作区的 glob/搜索能找到该路径,
请规范化该确切路径并重试。只有在确认读取器不可用、传输失败或路径规范化失败后,才使用 shell 文本读取器(Unix 上的 sed/cat,PowerShell 上的 Get-Content),并且必须先确认规范化后的路径仍在当前工作区内。对于内容排除、权限/策略、工作区边界或未知错误,请停止操作。审计所有通过许可读取器可访问的已发现文件;绝不要要求用户粘贴文件内容。如果所有许可的读取器都失败,请准确说明阻碍,不得绕过安全边界。
识别语言和框架。需要时,阅读对应的打包参考文件,并根据提供的目录解析其文件名。 只检查已知的参考目录,绝不要检查安装目录。 如果参考文件不可用,请使用下方目录,并说明参考文件受限的情况。
步骤 2:收集测试代码
在阅读代码主体前,先盘点已确定的范围。对于单个文件或类,直接读取该范围。对于项目或测试套件,只需发现一次测试文件;在工具允许时批量读取彼此独立的文件,并在所有已发现的测试和类级 fixture 都有台账处置记录后停止。
如果已加载扩展发现标记,请使用这些标记;否则使用此技能中的内置标记(例如 [TestClass]/[Fact]/[Test] 等属性、test_*.py、*.test.*、*_test.go、*_spec.rb、#[test]、*.Tests.ps1、TEST(...) 和 TEST_CASE(...))。
不要大范围读取无关的生产代码。对于每个可疑测试,都要打开对应的生产符号,以判断断言、转换、身份约定或相邻缺口是否确实存在。对于系统性的 facade/表面积模式,每个被调用的成员都相关:读取整个小型生产类型或检查每个被调用的成员,然后将每个薄弱测试映射到它应验证的确切可观察结果、异常、状态变化或边界。
步骤 3:扫描反模式
对照下方的反模式目录检查每个测试文件。按严重性分组报告发现项。如果已加载扩展映射,请使用这些映射;否则使用目录中的跨框架示例。
起草报告前,私下建立一份完整性台账,为每个测试方法以及每个类级 fixture/资源各记录一行。记录其判定依据(或缺失情况)、异常处理、状态/时间依赖、并发安全性、前置条件/断言顺序和处置结果。只有在每一行都已关联到某项发现或明确判定为无问题后,才能发布报告。尤其要注意:
actual != oldValue是薄弱的变更判定依据:它会接受所有错误的新值。必须要求精确的预期值。- 纳入未使用或未处置的类级资源;仅扫描方法会遗漏静态
HttpClient等字段。 - 如果测试读写的是未同步的静态/全局集合,则同时将其标记为顺序耦合和并行不安全。还要标记在旨在证明结果非空的断言之前就对可空结果进行解引用的情况。
- 提供了生产代码时,指出与发现项相邻的明显未测试约定,但不要进行穷尽式分支或变异分析。将更广泛的问题交由
test-gap-analysis处理。
严重——会造成虚假信心的测试
| 反模式 | 检查要点 |
|---|---|
| 没有断言 | 执行代码却从不进行任何断言的测试方法。没有断言的测试即使通过,也无法证明任何事情。在 .NET 中,检查是否缺少 Assert.*;在 pytest 中,检查函数是否没有 assert 和 pytest.raises;在 Jest 中,检查是否没有 expect(...);在 JUnit 中,检查是否没有 assert*/assertThat;在 Go 中,检查测试是否从未调用 t.Error*、t.Fatal* 或 testify;在 RSpec 中,检查代码块是否没有 expect;在 Pester 中,检查是否没有 Should。对模拟对象的调用进行验证(verify(mock)、expect(mock).toHaveBeenCalled、Should -Invoke)属于真正的断言。 |
| 异步断言缺少 await(JS/TS、.NET、Python、Kotlin、Swift) | expect(promise).resolves.toBe(x) 没有 await/return;pytest-asyncio 测试中有未等待的协程;async Task xUnit 测试调用 Assert.ThrowsAsync 却没有 await;Kotest 挂起测试没有使用 runTest;Swift Testing 异步测试没有使用 await。即使底层断言本应失败,这些测试也会悄无声息地通过。 |
| 为覆盖率而触碰代码 | 测试类逐一调用某个类型的每个公有成员,通常按字母顺序或声明顺序排列,却不对有意义的结果进行断言。每个测试通常执行 var result = sut.MethodName(...)(或 result = sut.method_name(...)、sut.methodName()、sut.MethodName(t)),没有断言,或者只做简单的 null/None/nil 检查。这样做的目的是抬高代码覆盖率指标,而非验证行为。这不同于单个没有断言的测试:其特征是系统性地覆盖整个接口,却没有真正的验证。 |
| 自指断言 | 预期值是根据同一个实际值计算得出的,例如 Assert.AreEqual(dto.Name, dto.Name)、Assert.AreEqual(result, result) 或等效写法。不要仅仅因为有效的同一性、克隆、序列化或往返契约会将输出与输入进行比较,就给它贴上此标签:这些断言可能会失败。相反,应检查输入是否触发了转换,以及是否缺少对已知表示形式、字段、引用同一性或无效输入的独立断言。 |
| 吞掉异常 | try { ... } catch { }、没有重新抛出异常或进行断言的 catch (Exception)(.NET);带 pass 的裸 except: 或 except Exception:(Python);try { ... } catch (e) {}(JS/TS/Java);没有重新触发 panic 且没有断言的 defer recover()(Go);没有断言的 rescue StandardError(Ruby);在测试中吞掉错误的 Result::unwrap_or(...)(Rust);空的 catch 块(Kotlin/Swift)。 |
| 仅在 catch 块中断言 | try { Act(); } catch (Exception ex) { Assert.Fail(ex.Message); }(以及其他语言中的等效写法)——请改用 Assert.ThrowsException / pytest.raises / expect(fn).toThrow / assertThrows / assert.Error(t, err) / #[should_panic] / Should -Throw / EXPECT_THROW。即使结果错误,只要没有抛出异常,测试就会通过。 |
| 恒为真的断言 | Assert.IsTrue(true)、Assert.AreEqual(x, x)、assert True、expect(true).toBe(true)、assert.True(t, true)、assert!(true) 或永远不会失败的条件。 |
| 被注释掉的断言 | 已禁用但测试仍会运行的断言,会造成测试覆盖充分的假象。 |
高风险——可能导致痛苦的测试
| 反模式 | 检查要点 |
|---|---|
| 不稳定性指标 | 使用墙上时钟休眠/等待进行同步:Thread.Sleep / Task.Delay(.NET)、time.sleep(Python)、setTimeout / await new Promise(r => setTimeout(...))(JS/TS)、Thread.sleep(Java/Kotlin)、time.Sleep(Go)、sleep(Ruby/Bash)、std::thread::sleep(Rust)、Start-Sleep(Pester)、std::this_thread::sleep_for(C++)。未经过抽象处理就读取墙上时钟:DateTime.Now/UtcNow、datetime.now()/datetime.utcnow()、Date.now() / new Date()、System.currentTimeMillis()、time.Now()、Time.now、Instant::now()、Date()/Date.now、Get-Date、std::chrono::system_clock::now。未设定种子的随机性:new Random()、random.random()/random.randint()、Math.random()、new Random()(Java/Kotlin)、未设定种子的 rand.Int()、rand(Ruby)、rand::random()(Rust)。依赖环境的路径(硬编码的 C:\...、/tmp/...、网络主机)。 |
| 测试顺序依赖 | 在多个测试间修改的静态/全局可变状态;未完全重置状态的设置代码([TestInitialize]、setUp、beforeEach、before(:each)、BeforeEach、t.Cleanup);单独运行时失败、但在测试套件中通过(或反之)的测试。各语言示例:static 字段(.NET/Java)、模块级全局变量(Python)、测试文件顶层的 let/const(JS/TS)、var 包级全局变量(Go)、类变量(Ruby)、static mut/lazy_static!/OnceCell(Rust)、$script: 变量(PowerShell)。 |
| 过度模拟 | 模拟设置代码行数多于实际测试逻辑。验证模拟对象上的精确调用顺序,而非结果。模拟测试自身拥有的类型。各语言示例:Moq/NSubstitute/FakeItEasy(.NET)、unittest.mock / pytest-mock(Python)、Jest 自动模拟 / Sinon(JS/TS)、Mockito/PowerMock(Java)、gomock/testify mock(Go)、RSpec mocks/mocha(Ruby)、mockall(Rust)、MockK(Kotlin)、Mock cmdlet(Pester)、gmock(C++)。如需在 .NET 中进行深入的模拟审计,请使用 exp-mock-usage-analysis。 |
| 与实现耦合 | 通过反射测试私有方法(MethodInfo.Invoke、Python 中的 getattr、TS 中的 (thing as any)、Java 中的 Field.setAccessible(true)、Ruby 中的 Object#send、Rust 中对内部 pub(crate) 的访问)。断言内部状态而非可观察行为。验证协作者上的精确方法调用次数,而非业务结果。 |
| 宽泛的异常断言 | Assert.ThrowsException<Exception>(...)(.NET)/ pytest.raises(Exception) / expect(fn).toThrow(Error)(未使用消息匹配器)/ assertThrows(Exception.class, ...)(Java)/ assert.Error(t, err)(未检查异常类型)/ expect { ... }.to raise_error(RSpec,未指定类)/ #[should_panic](未指定 expected = "...")/ Should -Throw(未指定 -ExpectedMessage)/ 使用 EXPECT_ANY_THROW 而非 EXPECT_THROW(stmt, SpecificType)。 |
| 薄弱的转换测试判定依据 | 规范化、大小写转换、修剪、映射或转换测试使用的输入本来就符合预期形式,因此即使实现什么都不做也能通过,尽管断言可能会捕获其他缺陷。使用必须发生变化的输入,并断言独立推导出的预期值。生产者/消费者往返测试很有用,但如果两端可能共享同一缺陷,就不能替代独立的格式断言。 |
中等 —— 可维护性和清晰度问题
| 反模式 | 检查要点 |
|---|---|
| 命名不佳 | 测试名称如 Test1、TestMethod 或 test,无法描述场景或结果。如果有已加载的扩展,请使用;否则遵循同一测试套件中现有的命名约定。 |
| 魔法值 | 准备或断言中出现没有解释的数字或字符串:Assert.AreEqual(42, result) / assert result == 42 / expect(result).toBe(42)——42 表示什么? |
| 重复测试 | 有 3 个或更多测试方法,主体几乎相同,只在一个输入值上有所不同。应改用参数化测试:[DataRow]/[Theory]/[TestCase](.NET)、@pytest.mark.parametrize(pytest)、test.each / it.each(Jest/Vitest)、@ParameterizedTest + @ValueSource(JUnit 5)、@DataProvider(TestNG)、Go 表驱动测试、where / 共享示例(RSpec)、#[rstest](Rust)、@ParameterizedTest + @MethodSource(Kotlin)、-ForEach / -TestCases(Pester)、INSTANTIATE_TEST_SUITE_P(GoogleTest)、SECTION / GENERATE(Catch2)、TEST_CASE_TEMPLATE(doctest)。在 .NET 中进行详细的重复代码分析,请使用 exp-test-maintainability。注意:覆盖不同边界条件的两个测试(例如,零与负数)不属于重复测试——针对不同边界情况分别编写测试,可以更清晰地诊断失败原因,也是有效做法。 |
| 巨型测试 | 测试方法超过约 30 行,或一次测试多种行为。失败时难以诊断。 |
| 重复断言内容的断言消息 | Assert.AreEqual(expected, actual, "Expected and actual are not equal") / assert x == y, "x is not equal to y" / assertEquals(x, y, "values not equal") 不会提供任何信息。消息应描述业务含义。 |
| 缺少 AAA / Given-When-Then 阶段划分 | 准备/执行/断言(Arrange/Act/Assert),或 BDD 框架(如 RSpec、Kotest behavior specs、Pester)中的 Given/When/Then 阶段相互交错,或无法区分。 |
低 —— 风格与规范问题
| 反模式 | 检查要点 |
|---|---|
| 未使用的测试基础设施 | 没有实际作用的初始化/清理钩子——[TestInitialize]/[SetUp]/[BeforeEach]、setUp/@BeforeEach/@BeforeAll、beforeEach/beforeAll、before(:each)/before(:all)、BeforeEach/BeforeAll(Pester)、setUpWithError(XCTest)——以及从未调用的测试辅助方法。 |
| 未管理的资源 | 测试创建了可释放/可关闭的资源,却未清理:HttpClient/Stream 未使用 using(.NET);文件/连接未使用 with 块或 try/finally(Python);FileInputStream 未使用 try-with-resources(Java);缺少 defer file.Close()(Go);连接未使用 ensure(Ruby);未依赖 Drop / 忘记调用 close(Rust);任何语言中临时文件 / 数据库缺少清理操作。 |
| 调试打印 | 测试开发过程中遗留的 Console.WriteLine / Debug.WriteLine / print() / console.log / System.out.println / fmt.Println / puts / dbg! / Write-Host / std::cout 语句。 |
| 命名约定不一致 | 同一测试类/模块/文件中混用不同的命名风格(例如,有些使用 Method_Scenario_Expected,另一些使用 ShouldDoSomething)。 |
第 4 步:如实校准严重性
报告前,请根据以下严重性规则重新检查每项发现:
- 严重/高:仅适用于会导致测试产生错误信心或不可靠的情况。无论正确与否都会始终通过的测试属于严重问题。共享可变状态存在潜在隔离风险时属于高严重性;但如果用户报告了实际的顺序依赖失败,或代码证明某项测试必须在另一项测试之后运行,则属于严重问题。异步断言缺少
await属于严重问题(会静默通过)。 - 中:仅适用于会实际损害可维护性的问题——5 个或更多几乎相同的测试、真正毫无意义的名称,例如
Test1/test/it1。 - 低:外观上的命名不一致、轻微的风格偏好、可以改进的断言消息。如有疑问,评为低。
- 始终一致地使用调用方的严重性术语。 如果调用方要求使用 严重 / 警告 / 信息,则将潜在可靠性风险映射为警告,将 可维护性或外观问题映射为信息。已证实的错误信心问题或当前顺序依赖的根本原因仍应评为严重;不要仅仅为了让每个要求的级别都有内容而降低严重性。严重性描述的是已证实的故障模式,而不是某项发现的文字篇幅。
- 将系统性发现与具体实例分开。 如果覆盖率问题涉及某个门面,所有受影响测试的证据应合并为一项系统性的严重发现。所有缺少断言的实例,包括最后一个门面方法,都应保留相同的错误信心严重性。报告为
1 finding / 6 affected tests,而不是六项发现再加一项总结;不要为了人为凑出多个级别而降低某个实例的严重性。 - 不要将普通的缺失用例按反模式评定严重性。 相邻的未测试分支、异常路径和边界条件可能是值得补充的覆盖机会,但除非某个薄弱的现有测试直接导致了缺口,否则应与反模式计数分开列出。即使测试套件存在系统性的严重问题,这些缺失用例本身也不属于严重问题。
- 不属于问题(按语言的特殊情况):
- Go 和 Rust 使用带子测试的表驱动循环(
t.Run/for case in cases { ... })是惯用写法,不属于“条件测试逻辑”。不要标记。 - pytest 中的裸
assert是标准断言形式,不属于缺少断言库。不要标记。 - Go 测试使用
if got != want { t.Errorf(...) }是标准的相等性检查,不属于临时拼凑。不要标记。 - 针对不同边界条件(零、负值、空值)分别编写测试,不属于重复测试。不要标记。
- 显式进行逐测试设置,而不是使用
[TestInitialize]/beforeEach(这会改善隔离性)。 - 测试简短清晰,只是理论上可以合并。
- 使用非平凡输入进行往返或序列化相等性检查,是有效的元变形证据,不是自我比较;但如果生产端和消费端可能存在相同缺陷,仍建议增加一种独立表示进行验证。
- 仅使用已转换过的输入来测试转换操作时,不要将其计入循环论证;但如果移除转换操作后测试仍会通过,则应报告其验证依据薄弱。应使用必须发生变化的输入,并明确独立预期结果。
- 克隆值相等性检查应保留;如果契约保证深拷贝,还应增加引用不同或修改互不影响的证据。
- 如果生产契约要求验证器或访问器原样返回输入值,则返回原值是正确行为。缺少无效输入用例属于覆盖率缺口,并不能证明现有断言是循环论证。
- Go 和 Rust 使用带子测试的表驱动循环(
重要:如果测试编写得很好,请在开头明确说明。不要为了让评审显得有价值而夸大严重性。没有严重/高严重性问题、只有一些轻微的低严重性建议,也是一种有效且有价值的评审结果。请先说明测试做得好的地方。
第 5 步:报告发现
深度标准——整洁但比未经辅助的审查浅的报告,就是失败。 撰写前,务必满足以下五点:
- 涵盖范围内的每项测试。 在总结前,先建立完整的方法/字段清单。对于覆盖触及这类系统性模式,至少逐一列出所有受影响的测试,而不是只给出有代表性的示例。悄然漏掉测试(或像未使用的
static HttpClient字段这样的 fixture)的发现表并不完整。注明审查数量。 - 评判测试预言前,先核实生产契约。 检查实际的转换、DTO 字段,以及承诺的标识/克隆语义。生产逻辑有意进行有损转换时,切勿臆造字段或要求无损往返。 对每个可疑的相等断言,在判定为发现之前,先写明独立已知的预期结果。如果断言将转换后的输出与非平凡输入、克隆状态、快照、模拟验证或框架原生断言上下文进行比较,说明它为什么可能失败,再判断它是否是恒真断言或完全没有断言。反过来,如果转换测试使用已经规范化的输入,就要指出该输入无法区分真实转换与无操作,并提供一个会发生变化的输入和精确的预期输出。对于成对的生产者/消费者 API,保留往返测试,并补充一个独立的表示形式预期结果,而不是替换有效的变形测试证据。
- 确保每项 Critical/High 修复完整且具体。 给出替代断言和精确的预期值(计算出的折扣、精确的 CSV 行、完整的预期对象),不要使用
// assert something here这样的占位符。 - 指出显而易见的相关缺口,但不要扩大到变异分析——提供了生产代码时,应在 相邻覆盖缺口 一节中直接指出相关但未测试的异常抛出、空结果、边界值以及往返/区域性敏感风险。要穷尽逐分支行为缺口,请使用
test-gap-analysis。 - 确保报告内部一致。 摘要计数必须与列出的发现相符。给出明确且最终的结论:写作前完成所有重新评估,输出中不要留下“等等,这不对”或“这应该失败但并没有”的推理过程。
- 明确说明哪些情况不构成问题。 对于整洁或大体良好的小型测试套件,指出已排除的可疑写法,以及使其有效的框架规则。不要让泛泛的检查清单或推测性改进建议掩盖清晰的结论。
按以下结构呈现发现:
- 摘要——发现的问题总数,并按严重性细分(Critical / High / Medium / Low)。如果测试编写良好,先说明这一点。
- Critical 和 High 级别发现——分别列出每项,并包含:
- 反模式名称
- 具体位置(文件、方法名、行号)
- 简要说明问题所在
- 具体修复方法(适用时展示修改前/后的代码)
- Medium 和 Low 级别发现——除非用户要求完整细节,否则用表格汇总
- 正面观察——指出测试中做得好的方面(密封类、具体异常类型、数据驱动测试、清晰的 AAA 结构、恰当使用伪实现、良好的命名)。不要只报告问题。
发布前,为每项发现分配稳定标识。分组行无论列出多少方法,都计为一项发现;独立的行分别计数。根据这些行重新计算摘要。将 affected tests 作为独立数字,以免捆绑发现导致计数不一致。
第 6 步:确定建议优先级
如果发现很多问题,请建议优先修复顺序:
- Critical——立即修复,这些测试可能会造成虚假的信心
- High——尽快修复,这些问题会导致不稳定或增加维护负担
- Medium/Low——在相关修改期间择机处理
验证
- 已涵盖范围内的每个测试方法(注明审查的数量;不得悄然跳过)
- 身份与往返测试的结论符合生产环境契约,且仅使用真实字段
- 每项发现都包含具体位置(不能只有笼统警告)
- 每项严重/高严重度发现都包含具体修复方案及精确的预期值
- 指出相邻但未测试的错误路径和边界值
- 摘要中的数量与列出的发现一致
- 分组发现区分发现数量与受影响的测试数量
- 不将相邻的覆盖机会夸大为严重反模式发现
- 报告涵盖所有类别(断言、隔离、命名、结构)
- 除问题外,也包含正面观察
- 按严重程度排列建议的优先级
常见陷阱
| 陷阱 | 解决方案 |
|---|---|
| 将风格问题报告为严重问题 | 命名和格式问题属于中/低严重度,绝不能列为严重问题 |
| 建议重写,而非有针对性的修复 | 展示最小差异——修改断言,而不是整个测试 |
| 指责有意为之的设计选择 | 如果集成测试中的 Thread.Sleep / time.sleep / time.Sleep 用于测试实际计时行为,则不属于反模式。请结合上下文判断。 |
| 在代码没有问题时捏造误报 | 如果测试遵循最佳实践,就应如实说明。审查结果为“0 项严重、0 项高、1 项低”完全合理。不要为了让审查显得有价值而夸大问题。 |
| 将单独的边界测试误判为重复项 | 测试零值和负值的两个测试覆盖的是不同边界情况。只有当 3 个或更多测试的主体完全相同,仅相差一个值时,才将其标记为重复项。 |
| 将外观问题评为中等严重度 | 命名不匹配(例如,方法名写着 ArgumentException,但断言的是 ArgumentOutOfRangeException)属于低严重度,而非中等严重度——测试仍能正确运行。 |
| 忽略测试框架 | 使用从语言扩展中加载的框架术语;不要用 MSTest 的术语描述 pytest 测试套件。 |
| 只见树木,不见森林 | 如果 80% 的测试都没有断言,应首先指出这一系统性问题,而不是逐一列出每个实例 |
| 为整洁牺牲深度 | 严重程度表和正面观察不能取代对每个测试的覆盖、修复方案中的精确预期值,以及相邻错误路径/边界缺口的说明 |
| 报告自相矛盾 | 先推理,再为每项发现写出明确结论——绝不能写出“等等,那不对”或“应该失败,但并没有”之类的反复推翻 |
| 统计数字对不上 | 摘要中的各严重程度总数必须与所列发现相符 |