Suche…


Zugriff auf private und geschützte Mitgliedervariablen

Reflection wird häufig im Rahmen von Softwaretests verwendet, z. B. zur Laufzeiterstellung / Instantiierung von Scheinobjekten. Es ist auch großartig, um den Status eines Objekts zu einem bestimmten Zeitpunkt zu überprüfen. Hier ein Beispiel für die Verwendung von Reflection in einem Komponententest, um zu überprüfen, ob ein geschützter Klassenmitglied den erwarteten Wert enthält.

Nachfolgend finden Sie eine sehr grundlegende Klasse für ein Auto. Es hat eine geschützte Elementvariable, die den Wert enthält, der die Farbe des Autos darstellt. Da die Membervariable geschützt ist, können wir nicht direkt darauf zugreifen und müssen eine Getter- und Setter-Methode verwenden, um ihren Wert abzurufen bzw. festzulegen.

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

Um dies zu testen, erstellen viele Entwickler ein Car-Objekt, legen die Farbe des Autos mit Car::setColor() , Car::setColor() die Farbe mit Car::getColor() und vergleichen diesen Wert mit der von ihnen festgelegten Farbe:

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

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

Oberflächlich scheint das okay zu sein. Schließlich Car::getColor() den Wert der geschützten Membervariablen Car::$color . Dieser Test ist jedoch auf zwei Arten fehlerhaft:

  1. Es übt Car::getColor() was sich außerhalb dieses Tests befindet
  2. Es hängt von Car::getColor() das einen Fehler enthalten kann, der dazu führen kann, dass der Test falsch positiv oder negativ ist

Schauen wir uns an, warum wir Car::getColor() in unserem Unit-Test nicht verwenden sollten und stattdessen Reflection verwenden sollten. Angenommen, ein Entwickler erhält die Aufgabe, jeder Fahrzeugfarbe "Metallic" hinzuzufügen. Sie versuchen also, Car::getColor() zu modifizieren, um "Metallic" der Farbe des Autos Car::getColor() :

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

Sehen Sie den Fehler? Der Entwickler verwendete ein Semikolon anstelle des Verkettungsoperators, um "Metallic" vor die Farbe des Autos zu setzen. Car::getColor() , wenn Car::getColor() aufgerufen wird, wird "Metallic" zurückgegeben, unabhängig von der tatsächlichen Farbe des Fahrzeugs. Daher Car::setColor() fehl , obwohl Car::setColor() einwandfrei funktioniert und von dieser Änderung nicht betroffen war .

Wie können wir also überprüfen, ob Car::$color den Wert enthält, den wir über Car::setColor() ? Wir können Refelection verwenden, um die geschützte Elementvariable direkt zu prüfen. Wie machen wir das ? Wir können Refelection verwenden, um die geschützte Membervariable für unseren Code zugänglich zu machen, damit der Wert abgerufen werden kann.

Sehen wir uns zuerst den Code an und zerlegen Sie ihn:

/**
 * @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);
}

So verwenden wir Reflection, um den Wert von Car::$color im obigen Code zu ermitteln:

  1. Wir erstellen ein neues ReflectionObject, das unser Car-Objekt darstellt
  2. Wir erhalten eine ReflectionProperty für Car::$color (dies repräsentiert die Variable Car::$color ).
  3. Wir machen Car::$color zugänglich
  4. Wir erhalten den Wert von Car::$color

Wie Sie mit Reflection sehen können, könnten wir den Wert von Car::$color ohne Car::getColor() oder eine andere Accessor-Funktion aufrufen zu müssen, die ungültige Testergebnisse verursachen könnte. Jetzt ist unser Car::setColor() für Car::setColor() sicher und genau.

Featureerkennung von Klassen oder Objekten

Die method_exists von Klassen kann teilweise mit den Funktionen property_exists und method_exists werden.

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

Mit einer ReflectionClass können auch Konstanten erkannt werden:

$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.

Hinweis: Für property_exists und method_exists kann anstelle des method_exists auch ein Objekt der interessierenden Klasse angegeben werden. Bei Verwendung der Reflektion sollte die ReflectionObject Klasse anstelle von ReflectionClass .

Testen privater / geschützter Methoden

Manchmal ist es nützlich, private und geschützte Methoden sowie öffentliche Methoden zu testen.

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

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

Die einfachste Möglichkeit, die Testmethode zu testen, ist die Reflektion

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

Wenn die Methode statisch ist, übergeben Sie null anstelle der Klasseninstanz

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow