framework - phpunit laravel
Mejores prácticas para probar métodos protegidos con PHPUnit (8)
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).
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 .
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.
Me gustaría proponer una ligera variación para getMethod () definida en la 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 ser explícito lo que se espera, pero eso depende de usted.
¡Aclamaciones!
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(...));
...
}
Sugiero la siguiente solución para la idea / solución de "Henrik Paul" :)
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 métodos privados prefijando y / o fijando un sufijo en alguna palabra común (por ejemplo _addPhpunit), de modo que cuando se llame al método __call () (ya 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.
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)
);