Testen Sie PHP-Header mit PHPUnit

97

Ich versuche, mit PHPunit eine Klasse zu testen, die einige benutzerdefinierte Header ausgibt.

Das Problem ist, dass auf meinem Computer Folgendes:

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

oder sogar das:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

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

        ob_clean();
    }
}

Geben Sie diesen Fehler zurück:

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.

Dies sieht so aus, als würde vor dem Ausführen des Tests etwas anderes auf dem Terminal ausgegeben, obwohl keine andere Datei enthalten ist und vor dem Beginn des PHP-Tags kein anderes Zeichen steht. Könnte es etwas in PHPunit sein, das dies verursacht?

Was könnte das Problem sein?

titel
quelle
14
Ich wollte dies nur behandeln, wenn es auch andere Leute gibt, die daran interessiert sind. headers_list () funktioniert nicht, während PHPunit (das PHP CLI verwendet) ausgeführt wird, aber xdebug_get_headers () funktioniert stattdessen.
Titel

Antworten:

123

Das Problem ist, dass PHPUnit einen Header auf dem Bildschirm druckt und Sie zu diesem Zeitpunkt keine weiteren Header hinzufügen können.

Die Problemumgehung besteht darin, den Test in einem isolierten Prozess auszuführen. Hier ist ein Beispiel

<?php

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

Dies führt zu:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

Der Schlüssel ist die Annotation @runInSeparateProcess.

Wenn Sie PHPUnit ~ 4.1 oder etwas verwenden und den Fehler erhalten:

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

Versuchen Sie, dies Ihrer Bootstrap-Datei hinzuzufügen, um das Problem zu beheben:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}
SamHennessy
quelle
7
Dies führt zu Fehlern, da einige define () -Anweisungen vorhanden sind. PHPUnit_Framework_Exception: Hinweis: Konstante xyz bereits definiert
Minhaz
1
@mebjas Das klingt nicht verwandt.
SamHennessy
4
Ich habe dies bei der Bewerbung erhalten: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
Dies ist definitiv die beste Option, um das Problem zu lösen. Es hat wie ein Zauber funktioniert!
Xarlymg89
2
Ich musste xdebug_get_headers () verwenden, um das Array der gesetzten Header zu erhalten. Die globale Funktion headers_list () hat in meinem Fall nicht funktioniert.
Shalom Sam
107

Obwohl das Ausführen des Tests in einem separaten Prozess das Problem behebt, entsteht beim Ausführen einer großen Testsuite ein spürbarer Overhead.

Mein Fix war, die Ausgabe von phpunit wie folgt an stderr zu leiten:

phpunit --stderr <options>

Dies sollte das Problem beheben und bedeutet auch, dass Sie keine Wrapper-Funktion erstellen und alle Vorkommen in Ihrem Code ersetzen müssen.

Jon Cairns
quelle
3
Brillant! Keine Änderungen an meinem Code, keine separaten Prozesse und es funktioniert.
Alexfernandez
Aber sehe ich immer noch Fehler, die ich gemacht habe, und erhalte die Ausgabe von error_reporting (E_ALL)?
Spankmaster79
1
@ spankmaster79 Ja, es wird nur der Standardfehler Ihres Terminals angezeigt. Standardmäßig drucken die meisten Terminals Standardausdruck und Standardfehler zusammen aus, es handelt sich jedoch tatsächlich um separate Streams.
Jon Cairns
Ich wurde gemacht !!! tnx für diesen trick ist es sinnvoll, fehler sollten dem stderror gemeldet werden !!! Tnx
th3n3rd
42
Sie können stderr="true"Ihre phpunit.xml hinzufügen , um einige Tastenanschläge zu speichern.
Tszming
9

Nebenbei: Für mich wurden headers_list()immer wieder 0 Elemente zurückgegeben. Ich habe den Kommentar von @titel zu dieser Frage bemerkt und festgestellt , dass er hier besondere Erwähnung verdient:

Ich wollte dies nur behandeln, wenn es auch andere Leute gibt, die daran interessiert sind. headers_list()funktioniert nicht während der Ausführung von PHPunit (das PHP CLI verwendet), sondern xdebug_get_headers()funktioniert stattdessen.

HTH

Melle
quelle
4

Wie bereits in einem Kommentar erwähnt, ist es meiner Meinung nach eine bessere Lösung, processIsolation in der XML-Konfigurationsdatei wie zu definieren

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

Auf diese Weise müssen Sie die Option --stderr nicht übergeben, was Ihre Mitarbeiter irritieren könnte.

PepeNietnagel
quelle
4
Es ist wahrscheinlich besser, es nur für den Test zu definieren, der es erfordert. Wenn Sie es für alle Tests festlegen, werden die Tests nur langsam ausgeführt.
Shi
3

Ich hatte eine radikalere Lösung, um sie $_SESSIONin meinen getesteten / enthaltenen Dateien zu verwenden. Ich habe eine der PHPUnit- Dateien unter ../PHPUnit/Utils/Printer.php bearbeitet , um "session_start();"vor dem Befehl "print $ buffer" eine zu haben .

Es hat bei mir wie ein Zauber funktioniert. Aber ich denke, die "Joonty" -Nutzerlösung ist die bisher beste von allen.

Sergio Abreu
quelle
Haben Sie die Pull-Anfrage im Repository eingereicht? Ich denke, dies könnte ein häufiges Problem sein.
t1gor
Vielen Dank für den Hinweis, ich habe session_start () in meiner phpunit bootstrap.php aufgerufen und es funktioniert für mich
bumperbox
0

Eine alternative Lösung zu @runInSeparateProcess besteht darin, die Option --process -isolation anzugeben, wenn PHPUnit ausgeführt wird:

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

Dies ist analog zum Setzen der Option processIsolation = "true" in phpunit.xml.

Diese Lösung hat ähnliche Vor- und Nachteile wie die Angabe der Option --stderr, die in meinem Fall jedoch nicht funktioniert hat. Grundsätzlich sind keine Codeänderungen erforderlich, auch wenn die Ausführung jedes Tests in einem separaten PHP-Prozess zu Leistungseinbußen führen kann.

AlexB
quelle
Dies ist möglicherweise ein erster Schritt, um die Ursache zu ermitteln und einige Tests durchzuführen. Um jedoch wartbare Tests zu erstellen, binden Sie die Anmerkung einfach direkt in die Testdatei ein. Je weniger spezielle Optionen über die Befehlszeile erforderlich sind, desto einfacher ist die Wartung einer CI-Systemkonfiguration.
Shi
wer hat jemals #fail herabgestuft. Diese Antwort ist als weitere Option korrekt.
fb
0

Verwenden Sie den Parameter --stderr, um nach Ihren Tests Header von PHPUnit abzurufen.

phpunit --stderr
NSukonny
quelle