PHPUnit 9.0+如何测试类受保护属性?以swapXY方法为例
问题解答
一、如何测试swapXY执行后属性的状态变化?
遵循OOP封装原则的前提下,优先通过类的公共行为间接验证,而非直接触碰内部属性,具体方案如下:
- 基于业务行为验证:如果
$x、$y的状态会影响类的某个公共方法输出,就用这个公共方法的结果判断交换是否生效。比如Some类有公共方法calculateResult(),其返回值依赖$x和$y的取值,那么执行swapXY()前后分别调用该方法,对比结果即可验证交换逻辑的正确性。这种方式完全符合封装原则,测试的是类的对外行为而非内部细节。 - 添加测试专属的安全访问方式:如果确实没有合适的公共行为可间接验证,可以在类中添加一个仅用于测试的方法,比如:
测试时定义class Some { protected $x; protected $y; // ... 其他业务方法 /** * @codeCoverageIgnore */ public function getTestCoordinates(): array { // 仅在测试环境下启用,生产环境误用会抛出异常 if (!defined('TESTING') || !TESTING) { throw new \RuntimeException('此方法仅用于测试'); } return ['x' => $this->x, 'y' => $this->y]; } }TESTING常量,调用这个方法获取属性状态做断言,同时用@codeCoverageIgnore标记避免影响代码覆盖率统计。 - (不推荐)反射作为临时妥协方案:如果以上两种方式都无法实现,才考虑反射,但要明确这是违背封装原则的权宜之计。示例代码:
public function testSwapXY() { $some = new Some(); // 反射设置初始值 $reflection = new \ReflectionObject($some); $xProp = $reflection->getProperty('x'); $xProp->setAccessible(true); $xProp->setValue($some, 10); $yProp = $reflection->getProperty('y'); $yProp->setAccessible(true); $yProp->setValue($some, 20); $some->swapXY(); $this->assertEquals(20, $xProp->getValue($some)); $this->assertEquals(10, $yProp->getValue($some)); }
二、为何反射被视为不良方案而从PHPUnit框架中移除?
核心原因是反射完全违背OOP的核心设计原则,带来一系列维护和设计问题:
- 破坏封装边界:类的受保护/私有属性属于内部实现细节,反射直接绕过类的对外接口访问这些细节,打破了类的封装承诺,让测试侵入了类的内部实现。
- 测试与实现过度耦合:依赖反射的测试会绑定到类的内部属性名、结构等细节上,一旦类的内部实现重构(比如把
$x、$y改成$coordinates数组),业务逻辑没变,但测试会直接失败,大幅增加重构成本。 - 违背SOLID原则:SOLID中的开闭原则要求类对修改关闭、对扩展开放,而反射测试强迫测试代码依赖内部实现,导致类的修改必须同步修改测试,违反了开闭原则;同时也不符合单一职责,测试本该关注业务行为,却变成了关注内部细节。
- 维护成本高昂:内部实现的变动频率通常远高于对外接口,依赖反射的测试需要频繁跟进修改,长期来看会大幅增加测试代码的维护负担。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

