Tester les en-têtes PHP avec PHPUnit

97

J'essaie d'utiliser PHPunit pour tester une classe qui génère des en-têtes personnalisés.

Le problème est que sur ma machine ceci:

<?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 même ceci:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

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

        ob_clean();
    }
}

renvoyer cette erreur:

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.

Cela ressemble à quelque chose d'autre sortant vers le terminal avant l'exécution du test, même s'il n'y a pas d'autre fichier inclus et qu'il n'y a pas d'autre caractère avant le début de la balise PHP. Cela pourrait-il être quelque chose à l'intérieur de PHPunit?

Quel pourrait être le problème?

titel
la source
14
Je voulais juste couvrir cela s'il y a d'autres personnes intéressées par cela également. headers_list () ne fonctionne pas lors de l'exécution de PHPunit (qui utilise PHP CLI) mais xdebug_get_headers () fonctionne à la place.
titel

Réponses:

123

Le problème est que PHPUnit imprimera un en-tête à l'écran et à ce stade, vous ne pouvez pas ajouter plus d'en-têtes.

La solution consiste à exécuter le test dans un processus isolé. Voici un exemple

<?php

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

Cela se traduira par:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

La clé est l'annotation @runInSeparateProcess.

Si vous utilisez PHPUnit ~ 4.1 ou quelque chose et obtenez l'erreur:

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

Essayez d'ajouter ceci à votre fichier d'amorçage pour le réparer:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}
SamHennessy
la source
7
cela provoque des erreurs dans certaines instructions define () PHPUnit_Framework_Exception: Remarque: la constante xyz est déjà définie
Minhaz
1
@mebjas Cela ne semble pas lié.
SamHennessy
4
J'ai obtenu ceci lors de l'application: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
C'est certainement la meilleure option pour résoudre le problème. Ça a marché comme sur des roulettes!
xarlymg89
2
J'ai dû utiliser xdebug_get_headers (), pour obtenir le tableau des en-têtes définis. La fonction globale headers_list () ne fonctionnait pas dans mon cas.
Shalom Sam
108

Bien que l'exécution du test dans un processus séparé résout le problème, il y a une surcharge notable lors de l'exécution d'une grande suite de tests.

Mon correctif était de diriger la sortie de phpunit vers stderr, comme ceci:

phpunit --stderr <options>

Cela devrait résoudre le problème, et cela signifie également que vous n'avez pas à créer une fonction wrapper et à remplacer toutes les occurrences de votre code.

Jon Cairns
la source
3
Brillant! Aucune modification de mon code, aucun processus séparé, et cela fonctionne.
alexfernandez
mais vais-je toujours voir les erreurs que j'ai faites et obtenir la sortie de error_reporting (E_ALL) ??
spankmaster79
1
@ spankmaster79 oui, cela ira simplement à l'erreur standard de votre terminal. Par défaut, la plupart des terminaux impriment ensemble l'erreur standard et l'erreur standard, mais ce sont en fait des flux séparés.
Jon Cairns
je devenais fait !!! tnx pour cette astuce, cela a du sens, les erreurs doivent être signalées au stderror !!! Tnx
th3n3rd
42
Vous pouvez ajouter stderr="true"votre phpunit.xml pour enregistrer quelques frappes.
tszming
9

En aparté: pour moi, j'ai headers_list()continué à retourner 0 éléments. J'ai remarqué le commentaire de @titel sur la question et j'ai pensé qu'il méritait une mention spéciale ici:

Je voulais juste couvrir cela s'il y a d'autres personnes intéressées par cela également. headers_list()ne fonctionne pas lors de l'exécution de PHPunit (qui utilise PHP CLI) mais xdebug_get_headers()fonctionne à la place.

HTH

Melle
la source
4

Comme déjà mentionné dans un commentaire, je pense que c'est une meilleure solution pour définir processIsolation dans le fichier de configuration XML comme

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

Comme ça, vous n'avez pas à passer l'option --stderr, ce qui pourrait irriter vos collègues.

PepeNietnagel
la source
4
Il est probablement préférable de le définir uniquement pour le test qui le nécessite. Le définir pour tous les tests ralentira simplement l'exécution des tests.
Shi
3

J'avais une solution plus radicale, afin de l'utiliser $_SESSIONdans mes fichiers testés / inclus . J'ai édité l'un des fichiers PHPUnit sur ../PHPUnit/Utils/Printer.php pour avoir un "session_start();"avant la commande "print $ buffer" .

Cela a fonctionné pour moi comme un charme. Mais je pense que la solution de l'utilisateur "joonty" est la meilleure de toutes jusqu'à présent.

Sergio Abreu
la source
Avez-vous soumis la demande d'extraction dans le référentiel? Je suppose que cela pourrait être un problème courant.
t1gor
Merci pour l'indice, j'ai appelé session_start () dans mon phpunit bootstrap.php et cela fonctionne pour moi
bumperbox
0

Une solution alternative à @runInSeparateProcess consiste à spécifier l'option --process-isolation lors de l'exécution de PHPUnit:

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

Cela revient à définir l'option processIsolation = "true" dans phpunit.xml.

Cette solution présente des avantages / inconvénients similaires à ceux de la spécification de l'option --stderr, qui n'a cependant pas fonctionné dans mon cas. Fondamentalement, aucune modification de code n'est nécessaire, même s'il peut y avoir un problème de performance en raison de l'exécution de chaque test dans un processus PHP distinct.

AlexB
la source
Cela peut être une première étape dans la recherche de la cause et la réalisation de certains tests, mais pour créer des tests maintenables, il suffit d'incorporer directement l'annotation dans le fichier de test. Moins il y a d'options «spéciales» requises à partir de la ligne de commande, plus la maintenance d'une configuration de système CI est facile.
Shi
qui a déjà déclassé #fail. cette réponse est correcte comme une autre option.
fb
0

Utilisez le paramètre --stderr pour obtenir les en-têtes de PHPUnit après vos tests.

phpunit --stderr
NSukonny
la source