Pruebe los encabezados PHP con PHPUnit

97

Estoy tratando de usar PHPunit para probar una clase que genera algunos encabezados personalizados.

El problema es que en mi máquina esto:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

        header('Location: foo');
        $headers_list = headers_list();
        header_remove();

        ob_clean();

        $this->assertContains('Location: foo', $headers_list);
    }
}

o incluso esto:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

        header('Location: foo');
        header_remove();

        ob_clean();
    }
}

devuelve este error:

name@host [~/test]# phpunit --verbose HeadersTest.php 
PHPUnit 3.6.10 by Sebastian Bergmann.

E

Time: 0 seconds, Memory: 2.25Mb

There was 1 error:

1) HeadersTest::testHeaders
Cannot modify header information - headers already sent by (output started at /usr/local/lib/php/PHPUnit/Util/Printer.php:173)

/test/HeadersTest.php:9

FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

Esto parece como si hubiera algo más saliendo en la terminal antes de que se ejecute la prueba, aunque no hay ningún otro archivo incluido y no hay ningún otro carácter antes del comienzo de la etiqueta PHP. ¿Podría ser algo dentro de PHPunit lo que está causando esto?

¿Cuál podría ser el problema?

título
fuente
14
Solo quería cubrir esto si hay otras personas interesadas en esto también. headers_list () no funciona mientras se ejecuta PHPunit (que usa PHP CLI) pero en su lugar funciona xdebug_get_headers ().
título

Respuestas:

123

El problema es que PHPUnit imprimirá un encabezado en la pantalla y en ese momento no podrá agregar más encabezados.

La solución consiste en ejecutar la prueba en un proceso aislado. Aquí hay un ejemplo

<?php

class FooTest extends PHPUnit_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     */
    public function testBar()
    {
        header('Location : http://foo.com');
    }
}

Esto resultará en:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

La clave es la anotación @runInSeparateProcess.

Si está utilizando PHPUnit ~ 4.1 o algo y obtiene el error:

PHP Fatal error:  Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in -:378
Stack trace:
#0 {main}
  thrown in - on line 378

Fatal error: Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378

Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378

Call Stack:
    0.0013     582512   1. {main}() -:0

Intente agregar esto a su archivo de arranque para solucionarlo:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}
SamHennessy
fuente
7
esto causa errores en algunas declaraciones define () PHPUnit_Framework_Exception: Aviso: Constante xyz ya definida
Minhaz
1
@mebjas Eso no suena relacionado.
SamHennessy
4
Recibí esto cuando se aplicó:PHP Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'SplFileInfo' is not allowed' in phar:///usr/local/bin/phpunit/phpunit/Util/GlobalState.php:211
t1gor
2
Esta es definitivamente la mejor opción para resolver el problema. ¡Funcionó a las mil maravillas!
xarlymg89
2
Tuve que usar xdebug_get_headers (), para obtener la matriz de encabezados establecidos. La función global headers_list () no funcionó en mi caso.
Shalom Sam
108

Aunque ejecutar la prueba en un proceso separado soluciona el problema, hay una sobrecarga notable cuando se ejecuta un gran conjunto de pruebas.

Mi solución fue dirigir la salida de phpunit a stderr, así:

phpunit --stderr <options>

Eso debería solucionar el problema y también significa que no tiene que crear una función contenedora y reemplazar todas las apariciones en su código.

Jon Cairns
fuente
3
¡Brillante! Sin cambios en mi código, sin procesos separados, y funciona.
alexfernandez
pero, ¿seguiré viendo los errores que cometí y obtendré la salida de error_reporting (E_ALL)?
spankmaster79
1
@ spankmaster79 sí, solo irá al error estándar de su terminal. De forma predeterminada, la mayoría de los terminales imprimirán la salida estándar y el error estándar juntos, pero en realidad son flujos separados.
Jon Cairns
me estaba haciendo !!! tnx para este truco, tiene sentido, los errores deben informarse al stderror !!! Tnx
th3n3rd
42
Puede agregar stderr="true"su phpunit.xml para ahorrar algunas pulsaciones de teclas.
tszming
9

Como comentario al margen: para mí headers_list()seguía devolviendo 0 elementos. Noté el comentario de @titel sobre la pregunta y pensé que merece una mención especial aquí:

Solo quería cubrir esto si hay otras personas interesadas en esto también. headers_list()no funciona mientras se ejecuta PHPunit (que usa PHP CLI) pero xdebug_get_headers()funciona en su lugar.

HTH

Melle
fuente
4

Como ya se mencionó en un comentario, creo que es una mejor solución para definir processIsolation en el archivo de configuración XML como

     <?xml version="1.0" encoding="UTF-8"?>
     <phpunit
        processIsolation            = "true"
        // ... 
     >
     </phpunit>

De esta manera, no tiene que pasar la opción --stderr, lo que podría irritar a sus compañeros de trabajo.

PepeNietnagel
fuente
4
Probablemente sea mejor definirlo solo para la prueba que lo requiera. Configurarlo para todas las pruebas solo hará que la ejecución de las pruebas sea más lenta.
Shi
3

Tenía una solución más radical, para usarla $_SESSIONdentro de mis archivos probados / incluidos . Edité uno de los archivos PHPUnit en ../PHPUnit/Utils/Printer.php para tener un "session_start();"antes del comando "print $ buffer" .

Me funcionó a las mil maravillas. Pero creo que la solución del usuario "joonty" es la mejor de todas hasta ahora.

Sergio Abreu
fuente
¿Enviaste la solicitud de extracción en el repositorio? Supongo que este podría ser un problema común.
t1gor
Gracias por la sugerencia, llamé a session_start () en mi phpunit bootstrap.php y funciona para mí
bumperbox
0

Una solución alternativa a @runInSeparateProcess es especificar la opción --process-aislamiento al ejecutar PHPUnit:

name@host [~/test]# phpunit --process-isolation HeadersTest.php

Eso es análogo a establecer la opción processIsolation = "true" en phpunit.xml.

Esta solución tiene ventajas / desventajas similares a las de especificar la opción --stderr, que sin embargo no funcionó en mi caso. Básicamente, no son necesarios cambios de código, aunque puede haber un impacto en el rendimiento debido a la ejecución de cada prueba en un proceso PHP separado.

AlexB
fuente
Este podría ser un primer paso para rastrear la causa y realizar algunas pruebas, pero para crear pruebas mantenibles, simplemente incruste directamente la anotación en el archivo de prueba. Cuantas menos opciones 'especiales' se requieran desde la línea de comando, más fácil será el mantenimiento de la configuración de un sistema CI.
Shi
que alguna vez rebajó #fail. esta respuesta es correcta como otra opción.
fb
0

Use el parámetro --stderr para obtener encabezados de PHPUnit después de sus pruebas.

phpunit --stderr
NSukonny
fuente