framework - phpunit tutorial español




Mejores prácticas para probar métodos protegidos con PHPUnit (6)

Encontré la discusión en ¿Prueba el método privado informativo?

He decidido, que en algunas clases, quiero tener métodos protegidos, pero probarlos. Algunos de estos métodos son estáticos y cortos. Debido a que la mayoría de los métodos públicos hacen uso de ellos, probablemente podré eliminar las pruebas de forma segura más adelante. Pero para comenzar con un enfoque TDD y evitar la depuración, realmente quiero probarlos.

Pensé en lo siguiente:

  • Método Objeto como se aconseja en una respuesta parece ser una exageración para esto.
  • Comience con métodos públicos y cuando la cobertura del código sea dada por pruebas de nivel superior, conviértalas en protegidas y elimine las pruebas.
  • Heredar una clase con una interfaz comprobable que hace públicos los métodos protegidos.

¿Cuál es la mejor práctica? ¿Hay algo mas?

Parece que JUnit cambia automáticamente los métodos protegidos para que sean públicos, pero no lo he analizado más a fondo. PHP no permite esto a través de la reflection .


Creo que Troelskn está cerca. Yo haría esto en su lugar:

class ClassToTest
{
   protected testThisMethod()
   {
     // Implement stuff here
   }
}

Luego, implementa algo como esto:

class TestClassToTest extends ClassToTest
{
  public testThisMethod()
  {
    return parent::testThisMethod();
  }
}

A continuación, ejecute sus pruebas contra TestClassToTest.

Debería ser posible generar automáticamente dichas clases de extensión analizando el código. No me sorprendería si PHPUnit ya ofrece tal mecanismo (aunque no lo he comprobado).


De hecho, puede usar __call () de forma genérica para acceder a métodos protegidos. Para poder probar esta clase.

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

creas una subclase en ExampleTest.php:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

Tenga en cuenta que el método __call () no hace referencia a la clase de ninguna manera, por lo que puede copiar lo anterior para cada clase con los métodos protegidos que desea probar y simplemente cambiar la declaración de la clase. Es posible que pueda colocar esta función en una clase base común, pero no la he probado.

Ahora, el caso de prueba solo difiere en el lugar en el que construye el objeto que se va a probar, cambiando en ExampleExposed por ejemplo.

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

Creo que PHP 5.3 le permite usar la reflexión para cambiar la accesibilidad de los métodos directamente, pero supongo que tendrá que hacerlo individualmente para cada método.


Parece que ya te has dado cuenta, pero lo repetiré de todos modos; Es una mala señal, si necesitas probar métodos protegidos. El objetivo de una prueba unitaria es probar la interfaz de una clase, y los métodos protegidos son detalles de implementación. Dicho esto, hay casos en los que tiene sentido. Si utiliza la herencia, puede ver una superclase como una interfaz para la subclase. Así que aquí, tendrías que probar el método protegido (pero nunca uno privado ). La solución a esto, es crear una subclase para propósitos de prueba, y usar esto para exponer los métodos. P.ej.:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Tenga en cuenta que siempre puede reemplazar la herencia con la composición. Cuando se prueba el código, generalmente es mucho más fácil lidiar con el código que usa este patrón, por lo que es posible que desee considerar esa opción.


Si está utilizando PHP5 (> = 5.3.2) con PHPUnit, puede probar sus métodos privados y protegidos utilizando la reflexión para configurarlos para que sean públicos antes de ejecutar sus pruebas:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

Voy a tirar mi sombrero en el anillo aquí:

He usado el truco __call con diversos grados de éxito. La alternativa que se me ocurrió fue usar el patrón de Visitante:

1: generar una clase stdClass o personalizada (para imponer el tipo)

2: cebar eso con el método y los argumentos requeridos

3: asegúrese de que su SUT tenga un método acceptVisitor que ejecutará el método con los argumentos especificados en la clase visitante

4: inyectalo en la clase que deseas probar

5: SUT inyecta el resultado de la operación en el visitante.

6: aplique sus condiciones de prueba al atributo de resultado del Visitante


teastburn tiene el enfoque correcto. Aún más simple es llamar directamente al método y devolver la respuesta:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

Puede llamar a esto simplemente en sus pruebas por:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );






phpunit