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?

titel
fonte
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');
}
SamHennessy
fonte
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.

Jon Cairns
fonte
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

Melle
fonte
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.

PepeNietnagel
fonte
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.

Sergio Abreu
fonte
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.

AlexB
fonte
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
NSukonny
fonte