ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

阿里 Java 开发手册单元测试规约全解:以 p3c 源码实践 AIR 与 BCDE 原则

阿里 Java 开发手册单元测试规约全解:以 p3c 源码实践 AIR 与 BCDE 原则 阿里 Java 开发手册单元测试规约全解以 p3c 源码实践 AIR 与 BCDE 原则【免费下载链接】p3cAlibaba Java Coding Guidelines pmd implements and IDE plugin项目地址: https://gitcode.com/gh_mirrors/p3/p3c本篇技术指南以《阿里巴巴 Java 开发手册》单元测试章节的 16 条规约为核心骨架结合开源项目 p3cAlibaba Java Coding Guidelines 的 PMD 实现与 IDE 插件中的真实测试代码、测试框架封装与构建配置逐条讲解单元测试的编写规范、工程组织与质量底线。读完本文你将掌握强制 / 推荐 / 参考三级规约的完整内涵理解 AIR 原则与 BCDE 原则在真实工程中的落地方式并能参照 p3c 仓库中src/test/java的测试结构与 PMD 测试框架写法为自己所在项目的单元测试建立可执行、可验证的规范基线。一、定位单元测试是开发手册中与开发者强相关的一章《阿里巴巴 Java 开发手册》将单元测试单列成章并在本章末尾第 16 条【参考】明确纠正一个普遍误解单元测试不是测试同学的事情手册面向开发同学其中每条内容都与开发者强相关。p3c 仓库本身即是这条规约的最佳例证——它的规则引擎实现p3c-pmd模块对自己的每一个 PMD 规则都编写了对应测试测试代码统一放在标准的src/test/java目录下见 p3c-pmd/src/test/java并用maven-pmd-plugin在verify阶段使用自身规则检查自身源码见 p3c-pmd/pom.xml。可以说p3c 项目既是规约的发布者也是规约的践行者。本章规约共 16 条其中 7 条【强制】、7 条【推荐】、2 条【参考】。下面按逻辑分组逐条展开并在每节以 p3c 仓库源码作为印证。二、强制基石AIR 原则与自动化、独立性、可重复性1. 【强制】好的单元测试必须遵守 AIR 原则AAutomatic自动化IIndependent独立性RRepeatable可重复手册用空气作比单元测试在线上运行时像空气一样不存在但在测试质量的保障上却非常关键。好的单元测试宏观上具有自动化、独立性、可重复执行三大特点。这三条正是后续第 2、3、4 条强制规约的总纲。p3c 的测试框架完全按这一原则搭建每个规则测试类都继承 PMD 测试框架的聚合器setUp()中通过addRule(ruleset, ruleName)批量注册待测规则例如 ConcurrentRuleTest.java 一次性注册ThreadPoolCreationRule、AvoidUseTimerRule、LockShouldWithTryFinallyRule等 9 条并发规则CommentRulesTest.java 注册 6 条注释规则。测试数据则独立放在 XML 文件中用expected-problems、expected-linenumbers声明期望的违规数与违规行号见 ThreadPoolCreationRule.xml运行完全交给构建工具自动触发人工只查看最终 pass/fail 结果。2. 【强制】单元测试必须全自动执行且非交互式测试用例通常被定期执行如每次 check in执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是好的单元测试测试中禁止使用System.out做人肉验证必须使用assert来验证。p3c 的测试即通过断言预期结果而非打印结果来验证规则行为对于 PMD 内置测试框架每个test-code声明expected-problems期望问题数与expected-linenumbers期望行号框架自动比对实际违规集合与期望集合不通过即测试失败p3c 额外封装了 ExtendRuleTst.java支持直接指定一个.java样例文件与期望违规行号字符串expectedVioLineNumbers内部将行号解析为ListInteger并构造TestDescriptor交给 PMD 的RuleTst执行见extractTestsFromJavaFile与getExpectedLineNumbers方法。以 UseRightCaseForDateFormatRuleTest.java 为例Test public void testExam1() { String ruleName UseRightCaseForDateFormatRule; String examFilePath java/ ruleName Exam.java; String expectedVioLineNumbers 16,26,32,34,36; Rule rule findRule(OtherRulesTest.RULESET, ruleName); runTest(rule, examFilePath, expectedVioLineNumbers); }它用期望违规行号16,26,32,34,36这一断言精确锁定了 UseRightCaseForDateFormatRuleExam.java 中YYYYMMDD、YYYY/MM/dd HH:mm:ss、YY-MM-DD、YY-md、Yy-md这 5 处反例而其余yyyy/MM/dd、yyyy-MM-dd HH:mm:ss等正例则必须零误报。这正是用 assert 代替人眼验证的工程化落地。3. 【强制】保持单元测试的独立性为保证测试稳定可靠且便于维护测试用例之间决不能互相调用也不能依赖执行的先后次序。反例method2依赖method1的执行结果作为自己的输入。PMD 测试框架天然满足这一点每个test-code都是独立片段code-fragment或 CDATA 内嵌代码互不引用、互不共享状态。在 p3c 的 XML 测试数据中可以看到大量独立的 code-fragment例如 ClassMustHaveAuthorRule.xml 里无 author 的类有 author 的类有 date 无 author 的类嵌套内部类枚举注解类型等十几个用例各自独立任何一个用例的执行顺序变化都不会影响其他用例的结果。4. 【强制】单元测试必须可重复执行不受外界环境影响单元测试通常被放进持续集成每次代码 check in 都会执行。如果单测依赖外部环境网络、服务、中间件等容易导致持续集成机制不可用。手册给出的正例是设计代码时就把 SUT被测系统的依赖改成注入测试时用 Spring 这样的 DI 框架注入本地内存实现或 Mock 实现。对应地p3c 的规则测试完全不触碰真实外部环境它只把一段 Java 源码字符串喂给 PMD 的 AST 解析与规则引擎输入是内存中的代码片段输出是违规列表既不连数据库、也不起服务。整个pmd-test依赖也被声明为test作用域见 p3c-pmd/pom.xml只在测试阶段参与构建确保测试在任何机器、任何时间重复执行结果一致。三、测试粒度与覆盖目标5. 【强制】保证测试粒度足够小单测粒度至多是类级别一般是方法级别。只有粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或跨系统的交互逻辑——那是集成测试的领域。p3c 的测试粒度严格停留在规则级别/方法级别每个测试类对应一个 PMD 规则如ThreadPoolCreationRule、ClassMustHaveAuthorRule每个test-code或每个testExam1()方法只验证一条规则在一个/一组代码片段上的行为从不跨越规则做联合验证。出问题时失败信息能直接定位到具体规则、具体用例甚至具体期望行号。6. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过新增代码要及时补充单元测试如果新增代码影响了原有单元测试要及时修正。这一条在 p3c 的构建管线中体现为自检闭环pom 中配置了maven-pmd-plugin在verify阶段执行check目标使用 p3c 自己的 10 个 rulesetali-comment、ali-concurrent、ali-naming、ali-oop等检查自己的源码并显式排除**/FixClassTypeResolver.java见 p3c-pmd/pom.xml。也就是说任何新增代码如果违反了手册规约mvn verify就会失败——增量代码确保通过被固化进了构建流程。8. 【推荐】单元测试的基本目标语句覆盖率达到 70%核心模块 100%语句覆盖率达到 70%核心模块的语句覆盖率和分支覆盖率都要达到 100%。工程规约应用分层中提到的 DAO 层、Manager 层、可重用度高的 Service都应该进行单元测试。这是本文档中唯一给出量化指标的规约是团队设定测试预算时的基线参考普通代码 70% 语句覆盖即可视为达标核心分层DAO / Manager / 高复用 Service则需语句与分支双 100%。实践中可借助 JaCoCo 等覆盖率插件在构建中产出报告并对齐该阈值。四、测试代码的组织与目录规约7. 【强制】测试代码必须写在 src/test/java不允许写在业务代码目录下原因是源码构建时会跳过此目录而单元测试框架默认扫描此目录。将测试与业务代码物理隔离既避免测试类被打进生产制品也让测试框架的自动发现机制按约定生效。p3c 仓库是这条规约的直接示范p3c-pmd/ ├── src/main/java/ # 规则实现业务/生产代码 ├── src/main/kotlin/ # Kotlin 规则实现 └── src/test/java/ # 全部测试代码 └── com/alibaba/p3c/pmd/lang/java/rule/ ├── comment/CommentRulesTest.java ├── concurrent/ConcurrentRuleTest.java ├── naming/NamingRulesTest.java ├── oop/OopRuleTest.java └── ...同时src/test/resources存放 XML 测试数据与 Java 样例文件如 UseRightCaseForDateFormatRuleExam.java。pom 中 Kotlin 插件把src/test/kotlin与src/test/java都纳入test-compile的sourceDirs见 p3c-pmd/pom.xml测试代码与生产代码在编译阶段即严格分离。五、BCDE 原则设计高质量测试用例9. 【推荐】编写单元测试代码遵守 BCDE 原则BCDE 是测试用例设计的四维框架保证被测试模块的交付质量BBorder边界值测试包括循环边界、特殊取值、特殊时间点、数据顺序等CCorrect正确的输入并得到预期的结果DDesign与设计文档相结合来编写单元测试EError强制错误信息输入如非法数据、异常流程、非业务允许输入等并得到预期的结果。以 p3c 的规则测试为实例Border 边界UseRightCaseForDateFormatRuleExam.java 覆盖了日期格式串的多种边界形态YYYY与yyyy周纪年 vs 年、MM与mm月 vs 分、HH与hh24 小时制 vs 12 小时制、大小写混写Yy-md、以及未被检测的模式yyy-md、Y-md——连规则不检查的情况也被显式列出防止误报Correct 正确输入yyyy/MM/dd、yyyy-MM-dd HH:mm:ss等合法模式期望 0 违规Error 错误输入YYYYMMDD、yy-MM-DD等非法模式期望在指定行号报违规Design 结合设计ClassMustHaveAuthorRule.xml按规则设计意图拆出普通类 / 枚举 / 注解类型 / 接口内枚举 / 嵌套内部类 / 非 public 类等场景逐一验证每个场景都有独立的 expected-problems。这种正例必须零误报、反例必须全命中的双向约束正是 BCDE 中 Correct 与 Error 两端在测试数据组织上的体现。六、数据库相关测试的实操规约10. 【推荐】数据库相关操作不能假设数据存在对于数据库相关的查询、更新、删除等操作不能假设数据库里的数据是存在的也不能直接操作数据库把数据插入进去而应使用程序插入或导入数据的方式来准备数据。反例删除某一行数据的单测先在数据库中手动增加一行作为删除目标但该行并不符合业务插入规则导致测试结果异常。11. 【推荐】数据库相关测试可设定自动回滚机制或对数据加明确的前后缀标识要么给测试配置自动回滚不给数据库造成脏数据要么对单测产生的数据使用明确的前后缀标识。手册给出的正例在 RDC 内部单元测试中使用RDC_UNIT_TEST_前缀标识数据。此类前缀约定让测试数据在业务数据中一眼可辨便于批量清理与排查。这两条与第 4 条可重复执行、不受外界环境影响一脉相承数据库是典型的外部依赖测试数据若不自给自足程序插入且可回收回滚/标识测试就无法在持续集成中稳定重复。七、可测性设计从测试反推代码质量12. 【推荐】对不可测的代码建议做必要重构避免为了达到测试要求而书写不规范测试代码。当代码难以测试时正确做法是重构被测代码使其可测而不是绕过测试或写出 hack 式的测试。这与第 4 条的把依赖改成注入一脉相承——依赖注入本身就是提升可测性的重构手段。15. 【参考】业务代码应避免的四种情况为了更方便地进行单元测试业务代码应避免构造方法中做的事情过多——构造即产生副作用使对象难以在测试中轻量构造存在过多的全局变量和静态方法——全局状态使测试之间相互污染破坏独立性存在过多的外部依赖——外部依赖越多测试越难隔离存在过多的条件语句——分支越多覆盖成本越高。手册说明多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。卫语句将深层嵌套的 if-else 拍平为先拦截异常/边界、后处理主流程的线性结构策略模式与状态模式则把复杂分支拆成可独立测试的小类从结构上把测试粒度降回方法级别。16. 【参考】不要对单元测试存在如下误解手册明确列出四种典型误解并逐一反驳误解正解那是测试同学干的事情本文是开发手册内容与开发同学强相关单元测试代码是多余的汽车整体功能与各单元部件的测试正常与否强相关单元测试代码不需要维护一年半载不维护单测几乎处于废弃状态单元测试与线上故障没有辩证关系好的单测能够最大限度地规避线上故障最后一条单测与线上故障的辩证关系在 p3c 的UseRightCaseForDateFormatRule上体现得淋漓尽致手册中Other规则的反例正是真实事故——有人用YYYY/MM/dd格式化日期导致2017/12/31被格式化成2018/12/31引发严重故障见 p3c-pmd/README.md 中该规则的说明。将这个事故固化为期望 5 处违规的测试用例就是用单测规避线上故障的直接实践。八、测试时机与范围管理13. 【推荐】设计评审阶段确定单元测试范围在设计评审阶段开发人员需要和测试人员一起确定单元测试范围单元测试最好覆盖所有测试用例UC。这要求单测范围在开发启动前就达成共识而不是开发完成后凭感觉补。14. 【推荐】不建议项目发布后补充单测建议在项目提测前完成单元测试作为一种质量保障手段不建议项目发布后再补应在提测前完成。理由显而易见提测前补测缺陷还能在测试与开发环节被拦截发布后补测单测已无法影响当次交付质量且业务代码可能已经演化补测成本更高。九、从 p3c 测试体系反观规约的落地形态p3c 仓库的测试体系是本文 16 条规约的一个紧凑缩影值得作为团队建设单测基础设施时的参照物目录规约第 7 条全部测试位于p3c-pmd/src/test/java测试资源位于src/test/resources与src/main/java严格隔离AIR 原则第 1~4 条测试输入为内存中的代码片段与 XML 数据无网络、无数据库、无时序依赖任何机器上可重复执行assert 驱动第 2 条expected-problems、expected-linenumbers、expectedVioLineNumbers三类断言精确比对期望与实际零System.out人肉验证BCDE 用例设计第 9 条正例零误报、反例全命中边界形态大小写混写、未覆盖模式显式建模可测性重构第 12 条p3c 扩展 PMD 的RuleTstExtendRuleTst.java与SimpleAggregatorTstExtendSimpleAggregatorTst.java把用样例文件测规则的诉求转化为可复用框架而不是为每个规则写一套 hack 测试增量代码保障第 6 条maven-pmd-plugin在verify阶段用自身规则集自检源码新增违规代码直接构建失败分层覆盖第 8 条按规则域comment / concurrent / constant / exception / flowcontrol / naming / oop / orm / other / set / vm组织测试类与 PMD ruleset 一一对应覆盖层次清晰可审计。团队在落地单元测试时可以直接以 p3c 的目录结构、测试框架选型PMD 的SimpleAggregatorTst/RuleTst 自研ExtendRuleTst与断言方式为蓝本再结合手册的量化指标语句覆盖 70%、核心模块双 100%与 BCDE 用例设计方法即可搭建出一套自动化、独立、可重复、可度量质量的单测基线。【免费下载链接】p3cAlibaba Java Coding Guidelines pmd implements and IDE plugin项目地址: https://gitcode.com/gh_mirrors/p3/p3c创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表