跳转到内容

NextPDF 的测试金字塔

Spec: ISO/IEC/IEEE 29119-4 Spec: ISO/IEC 25010 Evidence: Test-backed PHPStan: Level 10

NextPDF 并不只依赖一种测试。它有五个层级,每一层都回答关于这个引擎的不同问题。原因在于,一份 PDF 可以通过单元测试,却仍然是磁盘上一份结构损坏的文件。本页列出这五个层级,以及每一层分别负责证明什么。

PDF 引擎的失效面异常广。同一条代码路径可以在函数层面正确、在字节流层面也正确,却仍然产生一份合规阅读器会拒绝的文件。它也可能产生一份只在换页处出现细微渲染错误的文件。如果只在单一粒度上测试这个引擎,你获得的信心也只限于那个粒度,除此之外没有更多保证。

标准文献对此说得很清楚。基于规格与基于结构的测试设计技术彼此并不相关,因此建议测试策略采用不止一项准则,至少包括一项功能性准则和一项结构性准则(ISO/IEC/IEEE 29119-4, Annex A)。单一层级并不是良好策略的缩小版。它是一种不同的策略,而且是不完整的策略。

NextPDF 的测试由底部到顶端编排为五个层级:

  1. 单元(Unit)——单个类或函数,独立隔离。宽阔的底部。
  2. 集成(Integration)——跨模块边界协作的多个单元。
  3. 结构(Structural)——生成的 PDF 对象图、交叉引用表与尾部格式正确且符合规范。
  4. 视觉(Visual)——渲染后的页面在声明的容差范围内,与一份已批准的参考相符。
  5. 黄金文件(Golden)——固定的端到端样本,用于捕捉最终输出中的非预期偏移。顶端。

每一层都证明其下层无法证明的性质。没有一层是装饰性的。金字塔的形状讲的是数量——大量廉价的单元测试、较少昂贵的端到端测试——而不是重要性。

这些层级是真实落地的,而不是纸面愿景。这个代码库的 PHPUnit 配置将每一层都声明为一个具名测试套件,并与一个目录一对一对应。因此,每个层级都是测试执行器可以直接指向的位置,而不是幻灯片上的一个标签。资深工程师会熟悉的套件包括 UnitIntegrationGoldenSnapshotReproducibilityConformanceStandards 以及 Performance,每一个都有自己的执行配置(隔离方式、时间预算,以及是否在持续集成中默认执行)。

这种划分是刻意的。快速的底层(Unit)会在每次变更时执行,每个测试的预算为一秒。较慢、对环境敏感的层级——视觉渲染、完整一致性、性能——则采用选择性加入或每夜执行。这让常规路径保持快速且具确定性,同时不放弃更深入的检查。严格类型支撑着整个技术栈。这个引擎以 Spec: PHPStan, Level 10 进行分析,错误预算锁定为零,因此一大类缺陷根本不会进入测试阶段。

  1. Tier 1 of 5 Unit Isolated behaviour of a single class or function; the broad base.
  2. Tier 2 of 5 Integration Collaborating units across a module boundary.
  3. Tier 3 of 5 Structural The emitted PDF object/xref structure is well-formed and conformant.
  4. Tier 4 of 5 Visual Rendered output matches an approved reference within tolerance.
  5. Tier 5 of 5 Golden End-to-end byte/lossless fixtures pinned as the contract; the apex.
NextPDF 的五个测试层级,由底部到顶端。单元是宽阔、快速的底部;其上每一层都证明下层无法证明的性质,一路到顶端的黄金端到端固定样本。宽度只是数量的提示——它并未排定重要性的顺序。

Evidence: Test-backed 这五个套件在引擎的配置中以声明的 PHPUnit 测试套件形式存在,每一个都绑定到自己的目录与执行配置。本页使用的层级术语,与测试基础设施使用的术语一致。

Evidence: Standard-backed 采用不止一个层级的理由基于 Spec: ISO/IEC/IEEE 29119-4, Annex A : 覆盖准则并非全都彼此相关,因此建议策略结合功能性与结构性的技术。关键在于,同一份附录指出,覆盖准则之间的涵盖(subsumes)排序并不能显示它们揭露缺陷的能力,也就是测试有效性ISO/IEC/IEEE 29119-4, §C.2.4)。这也说明,「更多覆盖率」并不等同于 「更好的测试」。

Evidence: Standard-backed 选择要证明哪些性质, 对应到 Spec: ISO/IEC 25010 的产品质量特性:功能正确性(单元、集成),以及使一份 PDF 在下游真正可用的文件级性质 (结构、视觉、黄金文件)。该质量模型明确指出,不同的特性在不同的使用场景中各有其重要性。

这些层级可以通过引擎自己的脚本直接定位。对单个格式器的变更在底层验证。对文件外观(facade)的变更则跨多个层级验证:

<?php
declare(strict_types=1);
// Tier 1 — Unit: one unit, isolated, fast.
// composer test:unit → phpunit --testsuite Unit
// Tier 2 — Integration: collaborating units across a boundary.
// composer test:integration → phpunit --testsuite Integration
// Tier 3 — Structural: the emitted PDF object graph is well-formed.
// vendor/bin/phpunit --testsuite Conformance
// Tier 4/5 — Visual + Golden: rendered/serialized output vs a pinned
// reference (golden is byte/structure-pinned, never auto-updated).
// vendor/bin/phpunit --testsuite Golden
// A change to the document facade touches every API, so the routing
// guidance escalates it from "unit only" to the full unit + integration
// surface — the tier you run is a function of blast radius, not habit.

这个范例的重点在于路由逻辑,而不是那些命令。你要运行哪一层,取决于这项变更可能破坏什么。这套基础设施让每一层都成为一等的、可分别执行的目标。

金字塔常被解读成一种排名——单元测试在底部是因为它们最不重要、端到端在顶端是因为它们最重要(或反过来)。两者都不是。纵轴大致代表成本与数量:大量快速、廉价的单元测试构成宽阔的底部;越往上,测试数量越少、速度越慢、保真度越高。黄金测试并不比单元测试「更好」。它捕捉的是不同类型的失败:发现更晚、代价更高,而且用它来取代其下数以千计的快速检查,会是糟糕的选择。

第二个误解是,认为高覆盖率数字就代表金字塔健全。并非如此。覆盖率衡量的是执行,而不是检测。相关标准明确拒绝将覆盖率排序等同于发现缺陷的能力。那道落差正是变异测试存在所要揭露的。

本页描述的是这项策略的形状与意图,而不是它当前的结果。测试数量、覆盖率百分比与变异分数在此刻意不列出。它们是动态的质量信号,由持续集成产物生成。当前数字会随构建一起发布。如果将它们冻结成文字叙述,它们会悄然过时。唯一陈述的数字——PHPStan Level 10——是一项稳定的配置事实,可在引擎的静态分析配置中查证,并非一项度量值。

层级的名称是稳定的架构术语。套件的确切组成及其执行配置会随引擎演进,并由测试配置管理;如果测试配置与本说明有任何出入,以测试配置为准。本页并未主张任何特定的通过率,也未与任何其他代码库的测试策略进行比较。

  • 黄金文件测试——顶端层级如何固定参考输出并保持真实可信。
  • 变异测试详解——为什么覆盖率数字无法证明这些层级中的测试真的有效。
  • 处处严格类型——PHPStan Level 10 如何在任何层级执行之前就消除一整类缺陷。
  • 测试层级(Test tier)——策略中证明某类性质的层级(例如单元行为或结构有效性)。NextPDF 使用五个测试层级。
  • 结构测试(Structural test)——检查生成的 PDF 对象图、交叉引用表与尾部是否格式正确且符合规范,而非仅检查返回值。
  • 视觉测试(Visual test)——检查渲染后的页面是否在声明的容差范围内与一张已批准的参考图像相符。
  • 黄金测试(Golden test)——针对一份从不自动更新的固定参考输出所做的端到端检查;它是「输出未曾改变」的契约。
  • 测试有效性(Test effectiveness)——一组测试揭露缺陷的能力,ISO/IEC/IEEE 29119-4 将其与覆盖率区分开来。缩写说明:MSI(Mutation Score Indicator,变异分数指标)的定义见变异测试页面。
  • PHPStan Level 10——最严格的静态分析等级;NextPDF 以锁定为零的错误预算执行它。