Teste cabeçalhos de PHP com PHPUnit


97

Estou tentando usar o PHPunit para testar uma classe que gera alguns cabeçalhos personalizados.

O problema é que na minha máquina isso:

<?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);
    }
}

ou mesmo este:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

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

        ob_clean();
    }
}

retorna este erro:

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.

Parece que há outra saída no terminal antes da execução do teste, embora não haja nenhum outro arquivo incluído e nenhum outro caractere antes do início da tag PHP. Será que algo dentro do PHPunit está causando isso?

Qual poderia ser o problema?


14
Só queria abordar isso se houver outras pessoas interessadas nisso também. headers_list () não funciona durante a execução do PHPunit (que usa PHP CLI), mas xdebug_get_headers () funciona ao invés.
— título de

Respostas:


123

O problema é que o PHPUnit imprimirá um cabeçalho na tela e nesse ponto você não poderá adicionar mais cabeçalhos.

A solução alternativa é executar o teste em um processo isolado. Aqui está um exemplo

<?php

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

Isso resultará em:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

A chave é a anotação @runInSeparateProcess.

Se você estiver usando PHPUnit ~ 4.1 ou algo assim e obtiver o erro:

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

Tente adicionar isso ao seu arquivo de inicialização para corrigi-lo:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}

7
isto causa erros em algumas instruções define () PHPUnit_Framework_Exception: Aviso: Constante xyz já definida
— Minhaz

1
@mebjas Isso parece não relacionado.
— SamHennessy

4
Recebi isso quando me inscrevi: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
— 1gor

2
Esta é definitivamente a melhor opção para resolver o problema. Funcionou como um encanto!
— xarlymg89

2
Tive que usar xdebug_get_headers (), para obter a matriz de cabeçalhos definidos. A função global headers_list () não funcionou no meu caso.
— Shalom Sam

107

Embora a execução do teste em um processo separado corrija o problema, há uma sobrecarga perceptível ao executar um grande conjunto de testes.

Minha correção foi direcionar a saída do phpunit para stderr, assim:

phpunit --stderr <options>

Isso deve resolver o problema e também significa que você não precisa criar uma função de wrapper e substituir todas as ocorrências em seu código.


3
Brilhante! Sem alterações no meu código, sem processos separados e funciona.
— alexfernandez

mas ainda vou ver os erros que cometi e obter a saída do error_reporting (E_ALL) ??
— spankmaster79,

1
@ spankmaster79 sim, irá apenas para o erro padrão do seu terminal. Por padrão, a maioria dos terminais imprimirá a saída padrão e o erro padrão juntos, mas na verdade eles são fluxos separados.
— Jon Cairns,

eu estava me tornando feito !!! tnx para este truque, faz sentido, os erros devem ser reportados ao stderror !!! Tnx
— 3o 3o de

42
Você pode adicionar stderr="true"em seu phpunit.xml para salvar alguns pressionamentos de tecla.
— tszming de

9

Como um aparte: Para mim, headers_list()continuei retornando 0 elementos. Notei o comentário de @titel sobre a questão e achei que merece menção especial aqui:

Só queria abordar isso se houver outras pessoas interessadas nisso também. headers_list()não funciona durante a execução do PHPunit (que usa PHP CLI), mas xdebug_get_headers()funciona.

HTH


4

Como já mencionado em um comentário, acho que é uma solução melhor para definir o isolamento do processo no arquivo de configuração XML, como

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

Assim, você não precisa passar a opção --stderr, o que pode irritar seus colegas de trabalho.


4
Provavelmente, é melhor defini-lo apenas para o teste que o requer. Configurá-lo para todos os testes apenas tornará a execução dos testes lenta.
— Shi,

3

Tive uma solução mais radical, para usar $_SESSIONdentro dos meus arquivos testados / incluídos . Eu editei um dos arquivos PHPUnit em ../PHPUnit/Utils/Printer.php para ter um "session_start();"antes do comando "print $ buffer" .

Funcionou para mim como um encanto. Mas acho que a solução do usuário "joonty" é a melhor de todas até agora.


Você enviou a solicitação de pull no repositório? Acho que isso pode ser um problema comum.
— t1gor

Obrigado pela dica, chamei session_start () em meu phpunit bootstrap.php e funciona para mim
— bumperbox

0

Uma solução alternativa para @runInSeparateProcess é especificar a opção --process-isolation ao executar o PHPUnit:

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

Isso é análogo a definir a opção processIsolation = "true" em phpunit.xml.

Essa solução tem vantagens / desvantagens semelhantes à especificação da opção --stderr, que, entretanto, não funcionou no meu caso. Basicamente, nenhuma mudança de código é necessária, embora possa haver um impacto no desempenho devido à execução de cada teste em um processo PHP separado.


Esta pode ser uma primeira etapa para rastrear a causa e fazer alguns testes, mas para criar testes sustentáveis, basta incorporar diretamente a anotação no arquivo de teste. Quanto menos opções 'especiais' forem necessárias na linha de comando, mais fácil será a manutenção da configuração de um sistema de CI.
— Shi,

quem já desclassificou #fail. esta resposta está correta como outra opção.
— fb

0

Use o parâmetro --stderr para obter cabeçalhos do PHPUnit após seus testes.

phpunit --stderr
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.