make/accessible

一个非常轻量级的PHP包,让您轻松访问和测试单例类以及无法访问的实例成员,如私有或受保护的类方法和属性。

v0.1 2018-11-12 13:21 UTC

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);
    }
}

有关最佳实践的更多信息,请阅读我们的 最佳实践 文档。

支持

需要新功能?发现了错误?请打开一个 新问题 或发送我一份 电子邮件,我们会尽快修复。

贡献

请随时贡献,包括分支、修改和拉取请求。

致谢

许可

MakeAccessible 遵循 MIT 许可协议。有关更多信息,请参阅 许可文件