Поиск…


Доступ к частным и защищенным переменным-членам

Отражение часто используется как часть тестирования программного обеспечения, например, для создания / создания экземпляров макета. Это также отлично подходит для проверки состояния объекта в любой момент времени. Ниже приведен пример использования Reflection в модульном тесте для проверки того, что защищенный член класса содержит ожидаемое значение.

Ниже представлен очень простой класс для автомобиля. Он имеет защищенную переменную-член, которая будет содержать значение, представляющее цвет автомобиля. Поскольку переменная-член защищена, мы не можем получить к ней доступ напрямую и должны использовать метод getter и setter для извлечения и установки его значения соответственно.

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return $this->color;
    }
}

Чтобы проверить это, многие разработчики создадут объект «Автомобиль», установите цвет автомобиля с помощью Car::setColor() , извлеките цвет с помощью Car::getColor() и сравните это значение с установленным цветом:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    $getColor = $car->getColor();
        
    $this->assertEquals($color, $reflectionColor);
}

На первый взгляд это выглядит хорошо. В конце концов, все Car::getColor() возвращает значение защищенной переменной элемента Car::$color . Но этот тест испорчен двумя способами:

  1. Он использует Car::getColor() который выходит за рамки этого теста
  2. Это зависит от Car::getColor() который может иметь ошибку, которая может сделать тест ложным положительным или отрицательным

Давайте посмотрим, почему мы не должны использовать Car::getColor() в нашем модульном тесте и вместо этого должны использовать Reflection. Предположим, разработчику назначена задача добавить «Металлик» в каждый цвет автомобиля. Поэтому они пытаются модифицировать Car::getColor() чтобы добавить «Metallic» к цвету автомобиля:

class Car
{
    protected $color
    
    public function setColor($color)
    {
        $this->color = $color;
    }
    
    public function getColor($color)
    {
        return "Metallic "; $this->color;
    }
}

Вы видите ошибку? Разработчик использовал вместо запятой оператор с запятой, пытаясь добавить цвет «Металлик» к цвету автомобиля. В результате, когда вызывается Car::getColor() , «Metallic» будет возвращен независимо от фактического цвета автомобиля. В результате наш Car::setColor() тест Car::setColor() завершится неудачно, даже если Car::setColor() работает отлично и не повлиял на это изменение .

Итак, как мы можем подтвердить, что Car::$color содержит значение, которое мы устанавливаем через Car::setColor() ? Мы можем использовать Refelection для непосредственного контроля защищенной переменной-члена. Так как же нам делать? Мы можем использовать Refelection, чтобы сделать защищенную переменную-член доступной для нашего кода, чтобы она могла получить значение.

Сначала посмотрим на код, а затем сломаем его:

/**
 * @test
 * @covers     \Car::setColor
 */
public function testSetColor()
{
    $color = 'Red';

    $car = new \Car();
    $car->setColor($color);
    
    $reflectionOfCar = new \ReflectionObject($car);
    $protectedColor = $reflectionOfForm->getProperty('color');
    $protectedColor->setAccessible(true);
    $reflectionColor = $protectedColor->getValue($car);
    
    $this->assertEquals($color, $reflectionColor);
}

Вот как мы используем Reflection для получения значения Car::$color в приведенном выше коде:

  1. Мы создаем новый объект ReflectionObject, представляющий наш объект Car
  2. Мы получаем ReflectionProperty для Car::$color (это «представляет» переменную Car::$color )
  3. Мы делаем доступным доступный Car::$color
  4. Мы получаем значение Car::$color

Как вы можете видеть, используя Reflection, мы можем получить значение Car::$color без необходимости вызова Car::getColor() или любой другой функции доступа, которая может привести к недействительным результатам теста. Теперь наш блок-тест для Car::setColor() является безопасным и точным.

Определение функций классов или объектов

Функция обнаружения классов может частично выполняться с функциями property_exists и method_exists .

class MyClass {
    public $public_field;
    protected $protected_field;
    private $private_field;
    static $static_field;
    const CONSTANT = 0;
    public function public_function() {}
    protected function protected_function() {}
    private function private_function() {}
    static function static_function() {}
}

// check properties
$check = property_exists('MyClass', 'public_field');    // true
$check = property_exists('MyClass', 'protected_field'); // true
$check = property_exists('MyClass', 'private_field');   // true, as of PHP 5.3.0
$check = property_exists('MyClass', 'static_field');    // true
$check = property_exists('MyClass', 'other_field');     // false

// check methods
$check = method_exists('MyClass', 'public_function');    // true
$check = method_exists('MyClass', 'protected_function');    // true
$check = method_exists('MyClass', 'private_function');    // true
$check = method_exists('MyClass', 'static_function');    // true

// however...
$check = property_exists('MyClass', 'CONSTANT');  // false
$check = property_exists($object, 'CONSTANT');    // false

С ReflectionClass также могут быть обнаружены константы:

$r = new ReflectionClass('MyClass');
$check = $r->hasProperty('public_field');  // true
$check = $r->hasMethod('public_function'); // true
$check = $r->hasConstant('CONSTANT');      // true
// also works for protected, private and/or static members.

Примечание: для property_exists и method_exists , также является объект класса интереса может быть предоставлен вместо имени класса. С помощью отражения, то ReflectionObject класс следует использовать вместо ReflectionClass .

Тестирование частных / защищенных методов

Иногда полезно тестировать частные и защищенные методы, а также публичные.

class Car
{
    /**
     * @param mixed $argument
     *
     * @return mixed
     */
    protected function drive($argument)
    {
        return $argument;
    }

    /**
     * @return bool
     */
    private static function stop()
    {
        return true;
    }
}

Самый простой способ тестирования метода диска - использовать рефлексию

class DriveTest
{
    /**
     * @test
     */
    public function testDrive()
    {
        // prepare
        $argument = 1;
        $expected = $argument;
        $car = new \Car();

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('drive');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invokeArgs($car, [$argument]);

        // test
        $this->assertEquals($expected, $result);
    }
}

Если метод является статическим, вы передаете null вместо экземпляра класса

class StopTest
{
    /**
     * @test
     */
    public function testStop()
    {
        // prepare
        $expected = true;

        $reflection = new ReflectionClass(\Car::class);
        $method = $reflection->getMethod('stop');
        $method->setAccessible(true);

        // invoke logic
        $result = $method->invoke(null);

        // test
        $this->assertEquals($expected, $result);
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow