Pregunta

Encontré la discusión en ¿Pruebas 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é eliminarlos de manera segura más adelante. Pero para comenzar con un enfoque TDD y evitar la depuración, realmente quiero probarlos.

Pensé en lo siguiente:

  • Objeto del método como se indica en una respuesta parece ser una exageración para esto.
  • Comience con métodos públicos y, cuando las pruebas de nivel superior den cobertura de código, protéjalas 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 más?

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

¿Fue útil?

Solución

Si está utilizando PHP5 (> = 5.3.2) con PHPUnit, puede probar sus métodos privados y protegidos utilizando la reflexión para configurarlos como 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(...));
  ...
}

Otros consejos

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. Entonces, tendría 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. 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 manejar el código que usa este patrón, por lo que es posible que desee considerar esa opción.

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:

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

Me gustaría proponer una ligera variación para getMethod () definida en respuesta de uckelman .

Esta versión cambia getMethod () eliminando los valores codificados y simplificando un poco el uso. Recomiendo agregarlo a su clase PHPUnitUtil como en el ejemplo a continuación o a su clase que extiende PHPUnit_Framework_TestCase (o, supongo, globalmente a su archivo PHPUnitUtil).

Ya que MyClass está siendo instanciada de todos modos y ReflectionClass puede tomar una cadena o un objeto ...

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

También creé una función de alias getProtectedMethod () para que sea explícito lo que se espera, pero eso depende de usted.

¡Salud!

Creo que Troelskn está cerca. 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, ejecuta sus pruebas contra TestClassToTest.

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

Voy a lanzar 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: imprima eso con el método y los argumentos necesarios

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

De hecho, puede usar __call () de manera 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 te permite usar la reflexión para cambiar la accesibilidad de los métodos directamente, pero supongo que deberías hacerlo para cada método individualmente.

Sugiero seguir una solución para la "solución / idea de Henrik Paul" de ;

Sabes los nombres de los métodos privados de tu clase. Por ejemplo, son como _add (), _edit (), _delete () etc.

Por lo tanto, cuando desee probarlo desde el aspecto de la prueba unitaria, simplemente llame a los métodos privados con un prefijo y / o con el sufijo de alguna palabra común (por ejemplo _addPhpunit) para que cuando el método __call () sea llamado (dado que el método _addPhpunit () no existe) de la clase propietaria, simplemente coloque el código necesario en el método __call () para eliminar la / s palabra / s con prefijo / sufijo (Phpunit) y luego para llamar a ese método privado deducido desde allí. Este es otro buen uso de los métodos mágicos.

Pruébalo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top