test - phpunit tutorial español
Pruebas unitarias con elementos que necesitan enviar encabezados (11)
Actualmente estoy trabajando con PHPUnit para intentar desarrollar pruebas junto con lo que estoy escribiendo, sin embargo, actualmente estoy trabajando en la escritura del Administrador de sesiones, y tengo problemas para hacerlo ...
El constructor de la clase de manejo de la sesión es
private function __construct()
{
if (!headers_sent())
{
session_start();
self::$session_id = session_id();
}
}
Sin embargo, como PHPUnit envía texto antes de que comience la prueba, cualquier prueba en este objeto devuelve una prueba fallida, ya que los "encabezados" HTTP se han enviado ...
¿No puedes usar el buffer de salida antes de comenzar la prueba? Si almacenas en un búfer todo lo que se emite, no deberías tener problemas para configurar ningún encabezado, ya que hasta ese momento no se habría enviado ninguna salida al cliente.
Incluso si OB se utiliza en algún lugar dentro de sus clases, es apilable y OB no debe afectar lo que está pasando dentro.
Bueno, tu administrador de sesión está roto por diseño. Para poder probar algo, debe ser posible aislarlo de los efectos secundarios. Desafortunadamente, PHP está diseñado de tal manera que fomenta el uso liberal del estado global ( echo
, header
, exit
, session_start
, etc.).
Lo mejor que puede hacer es aislar los efectos secundarios en un componente, que pueden intercambiarse en tiempo de ejecución. De esta forma, sus pruebas pueden usar objetos simulados, mientras que el código directo usa adaptadores que tienen efectos secundarios reales. Descubrirás que esto no funciona bien con los singleton, que presumo que estás usando. Por lo tanto, deberá usar algún otro mecanismo para distribuir objetos compartidos a su código. Puede comenzar con un registro estático, pero hay soluciones aún mejores si no le importa un poco de aprendizaje.
Si no puede hacer eso, siempre tiene la opción de escribir pruebas de integración. P.ej. utilice el equivalente de PHPUnit de WebTestCase
.
Como estoy probando mi bootstrap en este momento (sí, sé que la mayoría de ustedes no lo hacen), estoy corriendo hacia el mismo problema (tanto header () como session_start ()). La solución que encontré es bastante simple, en tu bootper de unittest defines una constante y simplemente la compruebo antes de enviar el encabezado o iniciar la sesión:
// phpunit_bootstrap.php
define(''UNITTEST_RUNNING'', true);
// bootstrap.php (application bootstrap)
defined(''UNITTEST_RUNNING'') || define(''UNITTEST_RUNNING'', false);
.....
if(UNITTEST_RUNNING===false){
session_start();
}
Estoy de acuerdo en que esto no es perfecto por diseño, pero estoy probando una aplicación existente, no es deseable reescribir partes grandes. También uso la misma lógica para probar métodos privados usando los métodos mágicos __call () y __set ().
public function __set($name, $value){
if(UNITTEST_RUNNING===true){
$name=''_'' . $name;
$this->$name=$value;
}
throw new Exception(''__set() can only be used when unittesting!'');
}
Crea un archivo bootstrap para phpunit, que llama:
session_start();
Luego, inicie phpunit así:
phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/
El archivo bootstrap se llama antes que todo lo demás, por lo que el encabezado no se ha enviado y todo debería funcionar bien.
Creo que la solución "correcta" es crear una clase muy simple (tan simple que no necesita ser probada) que sea un envoltorio para las funciones relacionadas con la sesión de PHP, y usarla en lugar de llamar a session_start()
, etc. directamente.
En el objeto de simulación de pase de prueba en lugar de una clase de sesión con estado real y no comprobable.
private function __construct(SessionWrapper $wrapper)
{
if (!$wrapper->headers_sent())
{
$wrapper->session_start();
$this->session_id = $wrapper->session_id();
}
}
La creación del archivo bootstrap, señaló 4 publicaciones atrás parece ser la forma más limpia de evitar esto.
A menudo, con PHP tenemos que mantener e intentar agregar algún tipo de disciplina de ingeniería a los proyectos heredados que se construyen abismalmente. No tenemos el tiempo (o la autoridad) para deshacernos de todo el montón de basura y comenzar de nuevo, por lo que la primera respuesta de troelskn no siempre es posible como una forma de avanzar. (Si pudiéramos volver al diseño inicial, podríamos deshacernos de PHP y usar algo más moderno, como ruby o python, en lugar de ayudar a perpetuar este COBOL del mundo del desarrollo web).
Si intenta escribir pruebas unitarias para los módulos que usan session_start o setcookie en todos ellos, entonces comenzar la sesión en un archivo boostrap soluciona estos problemas.
Me pregunto por qué nadie ha incluido la opción de XDebug:
/**
* @runInSeparateProcess
* @requires extension xdebug
*/
public function testGivenHeaderIsIncludedIntoResponse()
{
$customHeaderName = ''foo'';
$customHeaderValue = ''bar'';
// Here execute the code which is supposed to set headers
// ...
$expectedHeader = $customHeaderName . '': '' . $customHeaderValue;
$headers = xdebug_get_headers();
$this->assertContains($expectedHeader, $headers);
}
Parece que necesita inyectar la sesión para que pueda probar su código. La mejor opción que he usado es Aura.Auth para el proceso de autenticación y usando NullSession y NullSegment para probar.
Pruebas de Aura con sesiones nulas
El marco de Aura está bellamente escrito y puedes usar Aura.Auth por sí mismo sin ninguna otra dependencia del marco de Aura.
Por lo que sé, Zend Framework usa el mismo buffer de salida para sus pruebas del paquete Zend_Session. Puede echar un vistazo a sus casos de prueba para comenzar.
Tuve el mismo problema y lo resolví llamando phpunit con --stderr flag así:
phpunit --stderr /path/to/your/test
Espero que ayude a alguien!
phpUnit imprime la salida a medida que se ejecutan las pruebas, lo que hace que headers_sent () devuelva verdadero incluso en su primera prueba.
Para superar este problema en un conjunto de pruebas completo, simplemente necesita usar ob_start () en su script de instalación.
Por ejemplo, supongamos que tiene un archivo llamado AllTests.php que es lo primero que carga phpUnit. Esa secuencia de comandos podría ser similar a la siguiente:
<?php
ob_start();
require_once ''YourFramework/AllTests.php'';
class AllTests {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite(''YourFramework'');
$suite->addTest(YourFramework_AllTests::suite());
return $suite;
}
}