make / accessible
一个非常轻量级的PHP包,让您轻松访问和测试单例类以及无法访问的实例成员,如私有或受保护的类方法和属性。
Requires (Dev)
- phpunit/phpunit: ^6.2
This package is auto-updated.
Last update: 2024-09-13 04:21:46 UTC
README
轻量级PHP包,让您测试单例类和无法访问的(私有或受保护的)实例成员。
目录
安装
composer require make/accessible
特性
- 调用无法访问的方法
- 将值设置到无法访问的属性中
- 从无法访问的属性中获取值
- 实例化单例类
用法
假设你有以下类
<?php use Person; /** * Class PeopleGreeter. * * Greats a list of persons, nothing more. */ class PeopleGreeter { /** @var Person[] */ protected $peopleToGreet = []; /** * Adds a person to `$peopleToGreat`. */ public function addPersonToGreet(Person $person) { $this->peopleToGreet[] = $person; } /** * Greats a `$person`. * * @param Person $person Person to great. */ protected function greet(Person $person) { if ($person->isWoman()) { return 'Hi Mrs. ' . $person->name(); } else { return 'Hello Mr. ' . $person->name(); } } /** * Greats everyone on `$peopleToGreat`. */ public function greetEveryone() { foreach ($this->peopleToGreet as $person) { echo $this->greet($person); } } }
这个类是正确的,它没有暴露其内部元素(封装),并且被设计来做它应该做的事情。
但是,你该如何测试这个类呢?测试 addPersonToGreet() 方法是否真的将 $person 添加到 $peopleToGreet 中,以及 greet() 方法是否真的能够区分男性和女性?
你只需将所有类成员设置为公开,但这样你会破坏 封装,这并不好。
那么,这里可以做什么呢?
解决方案
测试 addPersonToGreet() 的一种方法是通过直接访问 $peopleToGreet 并检查 $person 是否真的在那里。
$person = new Person('John Doe'); $peopleGreeter = new PeopleGreeter(); $peopleGreeter->addPersonToGreet($person); $this->assertTrue(in_array($person, $peopleGreeter->peopleToGreet));
测试 greet() 的一种方法是通过直接访问它。
$person = new Person('John Doe'); $this->assertEquals("Hello Mr. John Doe", (new PeopleGreeter())->greet($person));
但是,当然这些方法将失败,其结果将是
Error: Cannot access protected property PeopleGreeter::$peopleToGreet.
Error: Cannot access protected method PeopleGreeter::greet().
因为我们无法在 父作用域 外部访问 受保护的 和 私有的 成员。
另一种仅适用于 受保护成员 的方法是扩展其父类到另一个公开其方法的类
class ExtendedPeopleGreeter extends PeopleGreeter { public function greet($person) { return parent::greet($person); } public function getPeopleToGreet() { return parent::$peopleToGreet; } }
$person = new Person('John Doe'); $this->assertEquals("Hello Mr. John Doe", (new ExtendedPeopleGreeter())->greet($person));
$person = new Person('John Doe'); $peopleGreeter = new ExtendedPeopleGreeter(); $peopleGreeter->addPersonToGreet($person); $this->assertTrue(in_array($person, $peopleGreeter->getPeopleToGreet()));
优点
- 你可以测试受保护的成员。
缺点
- 不适用于私有成员。
- 使编写简单的测试变得非常困难。
- 每次你想测试一个真实类时,都需要编写一个伪造类。
- 你需要为每个你想测试的不可访问成员编写一个方法。
但当然,还有另一种解决方案:使用 PHP 反射
$person = new Person('John Doe'); $peopleGreeter = new PeopleGreeter(); $peopleGreeter->addPersonToGreet($person); $reflectedClass = new \ReflectionClass($peopleGreeter); $propertyName = 'peopleToGreet'; if ($reflectedClass->hasProperty($propertyName)) { $property = $reflectedClass->getProperty($propertyName); $property->setAccessible(true); $this->assertTrue(in_array($person, $property->getValue($peopleGreeter))); }
$person = new Person('John Doe'); $reflectedClass = new \ReflectionClass($peopleGreeter); $propertyName = 'peopleToGreet'; $peopleGreeter = new PeopleGreeter(); $reflectedClass = new \ReflectionClass($peopleGreeter); $methodName = 'greet'; if ($reflectedClass->hasMethod($methodName)) { $method = $reflectedClass->getMethod($methodName); $method->setAccessible(true); $returnedValue = $method->invokeArgs($peopleGreeter, ['person' => $person]); $this->assertEquals("Hello Mr. John Doe", $returnedValue); }
优点
- 适用于受保护和私有成员。
缺点
- 你需要编写大量的额外代码来制作简单的测试。
- 你需要编写也需要被测试的代码。
- 很难将此代码在其他测试或其他项目中重用。
我们正在测试2个方法,现在想想30个或50个?不,不……忘掉它吧!
使用 Make/Accessible
$person = new Person('John Doe'); $peopleGreeter = new PeopleGreeter(); $peopleGreeter->addPersonToGreet($person); $accessiblePeopleGreeter = Make::accessible($peopleGreeter); $this->assertTrue(in_array($person, $accessiblePeopleGreeter->peopleToGreet));
$person = new Person('John Doe'); $peopleGreeter = new PeopleGreeter(); $accessiblePeopleGreeter = Make::accessible($peopleGreeter); $this->assertEquals("Hello Mr. John Doe", $accessiblePeopleGreeter->greet($person));
你看到了吗???只需要一行代码,我们就做出了我们的 测试!!!
我们以一种常见且友好的方式获得了对 PeopleGreeter::class 不可访问成员的访问权! ;)
优点
- 用户友好。
- 只需一行代码。
- 使测试变得非常容易。
- 适用于私有和受保护的成员。
- 不需要为每个测试编写伪造类或方法。
- 鼓励在项目中使用封装,提供一个更隔离和灵活的环境。
缺点
- 😅
正如你所看到的,使用 Make/Accessible,你现在可以无需担心地使用封装你的项目了!
你还可以开始使用 Make/Accessible 测试单例类。功能文档即将推出。
最佳实践
我们强烈建议仅将此包用于测试目的。
避免使用此包来访问封装的类,因为这就像打破不想让你进入的人家的门。
如果你正在用相同的方式测试同一个类,我们建议在你的测试类底部创建一个函数 getAccessibleInstance(),在那里进行实例化、模拟等所有需要测试的类实例化操作。
class PeopleGreeterTest extends TestCase { public function testGreet() { $person = new Person('John Doe'); $peopleGreeter = $this->getAccessibleInstance(); $this->assertEquals("Hello Mr. John Doe", $peopleGreeter->greet($person)); } Others test functions... public function getAccessibleInstance() { $instance = new PeopleGreeter(); return Make::accessible($instance); } }
有关最佳实践的更多信息,请阅读我们的 最佳实践 文档。
支持
需要新功能?发现了错误?请打开一个 新问题 或发送我一份 电子邮件,我们会尽快修复。
贡献
请随时贡献,包括分支、修改和拉取请求。
致谢
- Eleandro Duzentos 及贡献者。
许可
MakeAccessible 遵循 MIT 许可协议。有关更多信息,请参阅 许可文件。