news 2026/9/23 4:32:01

PHP-CS-Fixer `@PHPUnit35Migration:risky` 规则集详解:PHPUnit 3.5 测试代码迁移实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP-CS-Fixer `@PHPUnit35Migration:risky` 规则集详解:PHPUnit 3.5 测试代码迁移实战
  • 开发工具
  • 代码质量
  • 静态分析
  • Lint
  • 格式化

【免费下载链接】PHP-CS-Fixer

A tool to automatically fix PHP Coding Standards issues

项目地址:https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer
点击查看免费下载

本篇技术指南以 PHP-CS-Fixer 官方规则集文档doc/ruleSets/PHPUnit35MigrationRisky.rst为核心,系统讲解用于改善 PHPUnit 3.5 兼容性的 risky 规则集@PHPUnit35Migration:risky:它的定位与风险等级、废弃状态与迁移路径、底层规则组成,以及在实际项目中如何启用、配置与验证。读完本文,你将掌握如何借助该规则集将旧式assertTrue(is_*())断言风格自动改写为 PHPUnit 3.5 专用的assertInternalType等断言,并理解规则集继承机制与集成测试的验证方式。

规则集定位:面向 PHPUnit 3.5 的测试代码改善

@PHPUnit35Migration:risky是 PHP-CS-Fixer 提供的一组面向 PHPUnit 3.5 兼容性的规则集,其官方定义(见 PHPUnit35MigrationRisky.rst)只有一句话:"Rules to improve tests code for PHPUnit 3.5 compatibility.",即通过自动化的代码风格修复,让测试代码更贴近 PHPUnit 3.5 时代推荐的断言 API。

这一规则集在仓库中的对应实现是 src/RuleSet/Sets/PHPUnit35MigrationRiskySet.php。从源码可以看到,它继承了AbstractMajorMinorDeprecationSetDefinition

final class PHPUnit35MigrationRiskySet extends AbstractMajorMinorDeprecationSetDefinition {}

类体为空,所有规则行为都由基类驱动。基类 src/RuleSet/AbstractMajorMinorDeprecationSetDefinition.php 的核心逻辑是:将当前规则集名称中 "数字.数字" 形式的版本号改写为 "数字x数字" 形式,从而把旧规则集直接委托给对应的新命名规则集

public function getRules(): array { $newName = Preg::replace('#(\d+)\.?(\d)#', '\1x\2', $this->getName()); return [ $newName => true, ]; } public function getSuccessorsNames(): array { return array_keys($this->getRules()); }

以本规则集为例,名称@PHPUnit35Migration:risky中的35会被转换为3x5,最终得到:

['@PHPUnit3x5Migration:risky' => true]

也就是说,@PHPUnit35Migration:risky本身只是一个兼容性别名,真正执行的规则全部来自@PHPUnit3x5Migration:riskygetSuccessorsNames()返回的 "继任者名称" 就是新规则集的名称,用于在 CLI 输出和文档中提示用户进行迁移。

重大警告:该规则集已废弃,请改用@PHPUnit3x5Migration:risky

官方文档在本规则集页面中给出了最醒目的警告

This rule set is DEPRECATED and will be removed in the next major version 4.0. You should use@PHPUnit3x5Migration:riskyinstead.

这包含两条关键信息:

  1. 废弃状态@PHPUnit35Migration:risky已被标记为 deprecated,并将在下一个主版本(4.0)中被移除。继续使用它虽然当前仍可工作,但会产生废弃警告。
  2. 替换方案:应当改用新命名的@PHPUnit3x5Migration:risky

之所以会有新老两套命名,可以从文档目录结构看出:规则集文档统一收录在 doc/ruleSets 下,既有PHPUnit35MigrationRisky.rst这样的老式 "数字" 命名文档,也有PHPUnit3x5MigrationRisky.rst这样的新式 "数字x数字" 命名文档;对应的源码集合同样成对存在(PHPUnit35MigrationRiskySet.php 与 PHPUnit3x5MigrationRiskySet.php)。新命名用x分隔主次版本号,语义更清晰、排序更稳定,是后续版本唯一保留的命名方式。

因此在实际项目中,请直接使用@PHPUnit3x5Migration:risky,而把@PHPUnit35Migration:risky视为历史遗留的别名,仅在阅读旧配置或迁移存量配置时理解其含义。

Risky 风险声明:使用前必须知悉的行为变更

与所有以:risky后缀结尾的规则集一样,官方文档明确声明:

This set contains rules that are risky. Using it may lead to changes in your code's logic and behaviour. Use it with caution and review changes before incorporating them into your code base.

含义是:该规则集包含 risky 规则,可能改变代码的逻辑与行为,需要谨慎使用,并在合入代码库前人工审查每一处改动。这是 PHP-CS-Fixer 对 risky 规则的统一约定——risky 规则通常基于"语义等价"假设进行改写,一旦代码存在特殊用法(例如重写了 PHPUnit 的原生断言方法),改写就可能破坏原有行为。

因此启用本规则集时,建议配合以下工程实践:

  • 在干净的 git 分支上运行,方便逐条 diff 审查;
  • 先在小范围(单个测试目录)试点,确认无行为回归后再全量应用;
  • 修复后立即运行完整测试套件,验证断言改写没有改变测试语义。

规则构成:从@PHPUnit3x5Migration:risky看完整规则链

虽然@PHPUnit35Migration:risky页面只列出一条链接(指向@PHPUnit3x5Migration:risky),但要让文章信息完整、可落地,需要深入其继任者的规则定义。查看 doc/ruleSets/PHPUnit3x5MigrationRisky.rst 与对应源码 src/RuleSet/Sets/PHPUnit3x5MigrationRiskySet.php,可以看到它包含两条规则:

public function getRules(): array { return [ '@PHPUnit3x2Migration:risky' => true, 'php_unit_dedicate_assert' => [ 'target' => PhpUnitTargetVersion::VERSION_3_5, ], ]; }

拆解如下:

  1. @PHPUnit3x2Migration:risky:规则集嵌套。它本身又包含@PHPUnit3x0Migration:riskyphp_unit_no_expectation_annotation(target 3.2)。因此整个继承链是@PHPUnit35Migration:risky@PHPUnit3x5Migration:risky@PHPUnit3x2Migration:risky@PHPUnit3x0Migration:risky,逐级向下兼容、逐级叠加规则。
  2. php_unit_dedicate_assert,配置['target' => '3.5']:将assertTrue(is_*())这类"通用断言"改写为 PHPUnit 3.5 时期专用的断言方法,是本规则集的核心动作。

值得注意,target在这里使用PhpUnitTargetVersion::VERSION_3_5常量(即字符串'3.5')。版本常量统一定义在 src/Fixer/PhpUnit/PhpUnitTargetVersion.php,从VERSION_3_0VERSION_11_0覆盖了 PHPUnit 3.0~11.0 的各个里程碑版本,外加VERSION_NEWEST'newest')。target决定修复器在"改写为哪个版本才存在的断言 API"上的取舍——例如assertInternalType在 PHPUnit 3.5 即已存在,而assertIsArrayassertDirectoryExists这类命名要到 PHPUnit 5.6 才引入,只有target达到对应版本时才会生成相应写法。

核心规则深入:php_unit_dedicate_assert的原理与改写效果

规则文档速览

php_unit_dedicate_assert的官方说明(见 doc/rules/php_unit/php_unit_dedicate_assert.rst)指出:像assertInternalTypeassertFileExists这类 PHPUnit 断言,应当取代通用的assertTrue。该规则:

  • 属于RISKY规则:如果代码中重写了 PHPUnit 的原生方法,修复可能产生风险;
  • 属于CONFIGURABLE规则:可通过target选项配置。

target选项的可选值为'3.0''3.5''5.0''5.6''newest',默认值为'newest'。本规则集将其固定为'3.5',即只改写为 PHPUnit 3.5 时代可用的断言形式。

改写映射表(源码级)

该修复器实现在 src/Fixer/PhpUnit/PhpUnitDedicateAssertFixer.php。它内部维护一张FIX_MAP常量表,将 PHP 内置函数/结构映射到对应的 PHPUnit 断言方法:

PHP 表达式(在assertTrue中)正断言负断言
array_key_existsassertArrayHasKeyassertArrayNotHasKey
emptyassertEmptyassertNotEmpty
file_existsassertFileExistsassertFileNotExists
is_array/is_bool/is_callable/is_float/is_int/is_numeric/is_object/is_resource/is_scalar/is_stringassertInternalType('...', $x)assertNotInternalType('...', $x)
is_dirassertDirectoryExistsassertDirectoryNotExists
is_infiniteassertInfiniteassertFinite
is_nanassertNan(无负断言)
is_nullassertNullassertNotNull
is_readableassertIsReadableassertNotIsReadable
is_writableassertIsWritableassertNotIsWritable
str_contains/str_ends_with/str_starts_withassertStringContainsString对应否定版本

其中is_arrayis_float等类型判断在 FIX_MAP 中标记为true,表示走"通用类型断言"分支,在target'3.5'时生成的是assertInternalType('array', $x)形式(assertInternalType正是 PHPUnit 3.5 引入的 API);而assertIsArray这类逐类型专名断言要到 PHPUnit 5.6 才出现,因此只有target >= '5.6'才会生成。

官方示例

规则文档给出默认配置(target'newest')下的改写示例:

- $this->assertTrue(is_float($a), "my message"); - $this->assertTrue(is_nan($a)); + $this->assertInternalType('float', $a, "my message"); + $this->assertNan($a);

以及['target' => '5.6']下的示例:

- $this->assertTrue(is_dir($a)); - $this->assertTrue(is_writable($a)); - $this->assertTrue(is_readable($a)); + $this->assertDirectoryExists($a); + $this->assertIsWritable($a); + $this->assertIsReadable($a);

对比可见target的核心影响:同样一句assertTrue(is_dir($a)),在target=3.5时改写结果取决于 3.5 是否具备对应专名断言,而target=5.6时可以生成assertDirectoryExists等更现代的断言。这也解释了为什么本规则集要把target固定在'3.5'——它只承诺 PHPUnit 3.5 兼容。

集成测试验证改写行为

仓库为@PHPUnit3x5Migration:risky提供了官方集成测试,可以直接看到真实输入输出:

  • 输入样例 tests/Fixtures/Integration/set/@PHPUnit3x5Migration-risky.test-in.php:
class FooTest extends \PHPUnit_Framework_TestCase { public function test_dedicate_assert($foo) { $this->assertTrue(is_null($foo)); $this->assertTrue(is_array($foo)); $this->assertTrue(is_nan($foo)); $this->assertTrue(is_readable($foo)); } /** * Foo. * @expectedException FooException * @expectedExceptionCode 123 */ function test_php_unit_no_expectation_annotation_32() { bbb(); } }
  • 输出样例 tests/Fixtures/Integration/set/@PHPUnit3x5Migration-risky.test-out.php:
class FooTest extends \PHPUnit_Framework_TestCase { public function test_dedicate_assert($foo) { $this->assertNull($foo); $this->assertInternalType('array', $foo); $this->assertTrue(is_nan($foo)); $this->assertTrue(is_readable($foo)); } /** * Foo. */ function test_php_unit_no_expectation_annotation_32() { $this->setExpectedException(\FooException::class, null, 123); bbb(); } }

这个例子非常直观地展示了规则集的整体行为:

  • assertTrue(is_null($foo))assertNull($foo)
  • assertTrue(is_array($foo))assertInternalType('array', $foo)这正是target=3.5与默认newest的差异所在,默认配置下is_array会被改写为assertIsArray);
  • assertTrue(is_nan($foo))assertTrue(is_readable($foo))未被改写——因为is_nanassertNanis_readableassertIsReadable均为 3.5 时代不存在的断言,target=3.5时安全地跳过了它们;
  • @expectedException/@expectedExceptionCode注解被转换为$this->setExpectedException(\FooException::class, null, 123);——这是继承自@PHPUnit3x2Migration:riskyphp_unit_no_expectation_annotation规则(target 3.2)的作用。

这正是 risky 规则"语义等价但需要人工确认"的典型体现:改写的确严格按 target 版本约束执行,不会引入目标版本不存在的 API。

如何在项目中启用该规则集

通过--rules命令行直接指定

PHP-CS-Fixer 支持在命令行直接传入规则集:

vendor/bin/php-cs-fixer fix tests/ --rules='@PHPUnit35Migration:risky'

如果不想触发废弃警告,应使用新命名:

vendor/bin/php-cs-fixer fix tests/ --rules='@PHPUnit3x5Migration:risky'

通过配置文件.php-cs-fixer.dist.php启用

在项目根目录的.php-cs-fixer.dist.php中配置:

<?php $finder = PhpCsFixer\Finder::create() ->in(__DIR__.'/tests'); return (new PhpCsFixer\Config()) ->setRules([ '@PHPUnit3x5Migration:risky' => true, ]) ->setFinder($finder) ->setRiskyAllowed(true) // 必须开启,否则 risky 规则集不会生效 ;

关键点@PHPUnit35Migration:risky@PHPUnit3x5Migration:risky均属于 risky 规则集。PHP-CS-Fixer 对 risky 规则采取默认关闭策略,只有当配置中显式设置setRiskyAllowed(true)(或命令行传入--allow-risky=yes)时才会实际执行。因此启用时请务必同时开启 risky 许可,否则规则集会被静默忽略。

与其他 PHPUnit 迁移规则集的关系

PHP-CS-Fixer 的 PHPUnit 迁移规则集构成一个完整的版本阶梯(见 doc/ruleSets 目录):@PHPUnit3x0Migration:risky@PHPUnit3x2Migration:risky@PHPUnit3x5Migration:risky一路延伸到@PHPUnit11x0Migration:risky。它们通过"低版本规则集嵌套进高版本规则集"的方式逐级叠加,保证迁移到任意目标版本时,所有低版本的兼容性改写都会被包含。@PHPUnit35Migration:risky(及废弃别名@PHPUnit35Migration:risky)正位于这条阶梯的第三级,是面向 PHPUnit 3.5 目标的聚合入口。

总结与使用建议

围绕@PHPUnit35Migration:risky,可以提炼出以下要点:

  1. 本质是别名:它在仓库源码中的实现为空壳类,通过AbstractMajorMinorDeprecationSetDefinition自动委托给@PHPUnit3x5Migration:risky,后者才真正聚合规则。
  2. 已废弃,勿在新配置中使用:官方明确声明将在 4.0 移除,新项目请直接使用@PHPUnit3x5Migration:risky;存量配置可借助getSuccessorsNames()的提示完成迁移。
  3. 风险自担:risky 规则集可能改变测试代码逻辑与行为,务必开启setRiskyAllowed(true)、逐条审查 diff、并在修复后回归完整测试。
  4. 行为受target约束:核心规则php_unit_dedicate_asserttarget=3.5下只生成 PHPUnit 3.5 已存在的断言(如assertInternalTypeassertNull),不会提前引入更高版本才有的assertIsArrayassertDirectoryExists等 API。
  5. 集成测试即行为规范:tests/Fixtures/Integration/set/@PHPUnit3x5Migration-risky.test 系列的输入输出对是官方对规则集行为的权威定义,任何对改写结果的疑问都可以在此求证。

如果你的测试代码仍停留在 PHPUnit 3.5 时代的assertTrue(is_*())风格,且需要在不引入高版本 API 的前提下保持兼容,那么@PHPUnit3x5Migration:risky@PHPUnit35Migration:risky的继任者)正是对口的自动化迁移工具——只需记得:启用 risky、审查 diff、跑通测试。

  • 开发工具
  • 代码质量
  • 静态分析
  • Lint
  • 格式化

【免费下载链接】PHP-CS-Fixer

A tool to automatically fix PHP Coding Standards issues

项目地址:https://gitcode.com/gh_mirrors/ph/PHP-CS-Fixer
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 4:31:48

插入排序算法详解:从Java实现到工程优化

1. 插入排序的直觉与本质&#xff1a;从打扑克说起如果你问我学排序算法第一步该学什么&#xff0c;我大概率会回答是插入排序&#xff0c;而不是很多人以为的冒泡排序。理由很简单&#xff1a;插入排序的思考方式和你日常生活中的行为习惯是最接近的&#xff0c;几乎不需要额外…

作者头像 李华
网站建设 2026/9/23 4:31:41

基于SSM的足球联赛管理系统与商城模块设计实现

1. 项目概述与设计思路1.1 这个系统到底要解决什么问题如果你在准备 Java 课程设计或者毕业设计&#xff0c;应该对“xx管理系统”这种题目不陌生。图书馆管理系统、学生管理系统、宿舍管理系统&#xff0c;满大街都是。但“足球联赛管理系统”加上“商城”两个关键词组合在一起…

作者头像 李华
网站建设 2026/9/23 4:31:34

tp安防源码性能优化实战:3招解决卡顿痛点

tp安防源码性能优化实战:3招解决卡顿痛点 面试时被问起“tp安防”在海量数据下的响应机制,是不是脑子一片空白?明明代码能跑,但一上生产环境就卡得跟PPT似的。其实, tp安防 这类高并发场景下的性能问题,核心不在于功能实现,而在于对底层I/O和内存管理的深刻理解。今天不聊虚的,直接拆解一个典型的…

作者头像 李华
网站建设 2026/9/23 4:31:34

天玑8100等于骁龙多少:拆解高频面试题背后的性能陷阱

天玑8100等于骁龙多少:拆解高频面试题背后的性能陷阱 复制来的代码跑不通不知道怎么调?这不仅仅是你一个人的噩梦。很多开发者盯着报错信息发呆,明明逻辑看着对,一运行就崩。其实,这背后往往藏着对底层硬件性能的误解。就像在面试中被问到“天玑8100等于骁龙多少”这种看似简单实则高频面试题,很多人只知结果…

作者头像 李华
网站建设 2026/9/23 4:31:29

红流图解原理:3个坑点让你面试不再卡壳

红流图解原理:3个坑点让你面试不再卡壳 面试官问:“红流的核心机制是什么?为什么并发下会乱序?”你愣了三秒,大脑一片空白。这种时刻,背八股文毫无用处,因为没人听你复述定义。真正拉开差距的,是你能否用图解原理的方式,把底层逻辑讲清楚。在掘金技术社区的许多高赞帖子中,老手们反复强调:原理不清,代码必崩。…

作者头像 李华
网站建设 2026/9/23 4:31:17

5个w面试必考题:Python项目搭建失败避坑与完整示例解析

5个w面试必考题:Python项目搭建失败避坑与完整示例解析 刚啃完Python语法书,看着Hello World跑通就觉得自己无敌了?结果一搭真实项目,依赖装不上、环境乱套、报错满天飞,瞬间懵圈。别慌,这种“语法会了但项目搭不起来”的困境,90%的应届生都踩过。今天直接拆解大厂面试高频考点【5个w…

作者头像 李华