php - tutorial - pruebas unitarias y métodos estáticos
tutorial php unit (3)
Los métodos estáticos en sí mismos no son más difíciles de probar que los métodos de instancia. El problema surge cuando un método, estático o no, llama a otros métodos estáticos porque no se puede aislar el método que se está probando. Aquí hay un método de ejemplo típico que puede ser difícil de probar:
public function findUser($id) {
Assert::validIdentifier($id);
Log::debug("Looking for user $id"); // writes to a file
Database::connect(); // needs user, password, database info and a database
return Database::query(...); // needs a user table with data
}
¿Qué quieres probar con este método?
- Al pasar algo que no sea un entero positivo, se arroja
InvalidIdentifierException
. -
Database::query()
recibe el identificador correcto. - Un usuario coincidente se devuelve cuando se encuentra,
null
cuando no.
Estos requisitos son simples, pero también debe configurar el registro, conectarse a una base de datos, cargarlo con datos, etc. La clase de Database
debe ser la única responsable de las pruebas que pueden conectarse y consultar. La clase Log
debería hacer lo mismo para el registro. findUser()
no debería tener que lidiar con nada de esto, pero debe hacerlo porque depende de ellos.
Si, en cambio, el método anterior realizara llamadas a métodos de instancia en instancias de Database
de Database
y Log
, la prueba podría pasar en objetos simulados con valores de retorno con guiones específicos para la prueba en cuestión.
function testFindUserReturnsNullWhenNotFound() {
$log = $this->getMock(''Log''); // ignore all logging calls
$database = $this->getMock(''Database'', array(''connect'', ''query'');
$database->expects($this->once())->method(''connect'');
$database->expects($this->once())->method(''query'')
->with(''<query string>'', 5)
->will($this->returnValue(null));
$dao = new UserDao($log, $database);
self::assertNull($dao->findUser(5));
}
La prueba anterior fallará si findUser()
llamar a connect()
, pasa el valor incorrecto para $id
( 5
arriba) o devuelve algo que no sea null
. Lo bueno es que no se trata de ninguna base de datos, lo que hace que la prueba sea rápida y robusta, lo que significa que no fallará por motivos ajenos a la prueba, como fallas en la red o datos de muestras incorrectos. Le permite enfocarse en lo que realmente importa: la funcionalidad contenida en findUser()
.
Leer y retomar las pruebas unitarias, tratar de dar sentido a la siguiente publicación explica las dificultades de las llamadas a funciones estáticas.
No entiendo claramente este problema. Siempre he supuesto que las funciones estáticas eran una buena forma de redondear las funciones de utilidad en una clase. Por ejemplo, a menudo uso llamadas de funciones estáticas para inicializar, es decir:
Init::loadConfig(''settings.php'');
Init::setErrorHandler(APP_MODE);
Init::loggingMode(APP_MODE);
// start loading app related objects ..
$app = new App();
// Después de leer la publicación, ahora apunto a esto en cambio ...
$init = new Init();
$init->loadConfig(''settings.php'');
$init->loggingMode(APP_MODE);
// etc ...
Pero, las pocas docenas de pruebas que había escrito para esta clase son las mismas. No cambié nada y todavía todos pasan. ¿Estoy haciendo algo mal?
El autor de la publicación dice lo siguiente:
El problema básico con los métodos estáticos es que son códigos de procedimiento. No tengo idea de cómo probar el código de procedimiento de la unidad. Las pruebas unitarias asumen que puedo crear una instancia de una pieza de mi aplicación de forma aislada. Durante la instanciación conecto las dependencias con mocks / friendlies que reemplazan las dependencias reales. Con la programación de procedimientos no hay nada que "cablear" ya que no hay objetos, el código y los datos están separados.
Ahora, entiendo por la publicación que los métodos estáticos crean dependencias, pero no entiendo intuitivamente por qué uno no puede probar el valor de retorno de un método estático tan fácilmente como un método regular.
Evitaré los métodos estáticos, pero me gustaría tener una idea de CUÁNDO los métodos estáticos son útiles, si es que lo son. Parece que desde este post los métodos estáticos son tan malvados como las variables globales y deben evitarse tanto como sea posible.
Cualquier información adicional o enlaces sobre el tema sería muy apreciada.
No veo ningún problema al probar métodos estáticos (al menos ninguno que no existe en métodos no estáticos).
- Los objetos falsos se pasan a las clases bajo prueba usando la inyección de dependencia.
- Los métodos estáticos falsos se pueden pasar a las clases bajo prueba utilizando un autocargador adecuado o manipulando el
include_path
. - La vinculación estática tardía trata con métodos que invocan métodos estáticos en la misma clase.
Sebastian Bergmann está de acuerdo con Misko Hevery y lo cita con frecuencia:
La unidad de prueba necesita costuras, uniones es donde impedimos la ejecución de la ruta de código normal y es la forma de lograr el aislamiento de la clase bajo prueba. Las costuras funcionan a través del polimorfismo, anulamos / implementamos clase / interfaz y luego conectamos la clase bajo prueba de manera diferente para tomar el control del flujo de ejecución. Con los métodos estáticos no hay nada que anular. Sí, los métodos estáticos son fáciles de llamar, pero si el método estático llama a otro método estático, no hay manera de anular la dependencia del método llamado.
El problema principal con los métodos estáticos es que introducen el acoplamiento, por lo general codificando en forma rígida la dependencia en su código de consumo, lo que hace que sea difícil reemplazarlos con trozos o burlas en sus Pruebas unitarias. Esto viola el Principio Abierto / Cerrado y el Principio de Inversión de Dependencia , dos de los principios SÓLIDOS .
Tienes toda la razón en que la estática se considera dañina . Evítales.
Consulte los enlaces para obtener información adicional, por favor.
Actualización: tenga en cuenta que, si bien la estática todavía se considera dañina, la capacidad de stub y simulación de métodos estáticos se ha eliminado a partir de PHPUnit 4.0