ทดสอบส่วนหัวของ PHP ด้วย PHPUnit


98

ฉันกำลังพยายามใช้ PHPunit เพื่อทดสอบคลาสที่แสดงผลส่วนหัวที่กำหนดเอง

ปัญหาคือบนเครื่องของฉันสิ่งนี้:

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

หรือแม้แต่สิ่งนี้:

<?php

class HeadersTest extends PHPUnit_Framework_TestCase {

    public function testHeaders()
    {
        ob_start();

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

        ob_clean();
    }
}

ส่งคืนข้อผิดพลาดนี้:

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.

ดูเหมือนว่ามีสิ่งอื่นส่งออกไปยังเทอร์มินัลก่อนที่การทดสอบจะทำงานแม้ว่าจะไม่มีไฟล์อื่นรวมอยู่และไม่มีอักขระอื่นก่อนที่จะเริ่มแท็ก PHP อาจเป็นสิ่งที่อยู่ใน PHPunit ที่ทำให้เกิดสิ่งนี้หรือไม่?

ปัญหาคืออะไร?


14
แค่อยากจะพูดถึงเรื่องนี้หากมีคนอื่นสนใจเรื่องนี้เช่นกัน headers_list () ไม่ทำงานในขณะที่เรียกใช้ PHPunit (ซึ่งใช้ PHP CLI) แต่ xdebug_get_headers () ทำงานแทน
titel

คำตอบ:


124

ปัญหาคือ PHPUnit จะพิมพ์ส่วนหัวไปยังหน้าจอและ ณ จุดนั้นคุณไม่สามารถเพิ่มส่วนหัวได้อีก

วิธีแก้ปัญหาคือเรียกใช้การทดสอบในกระบวนการแยก นี่คือตัวอย่าง

<?php

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

สิ่งนี้จะส่งผลให้:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

ที่สำคัญคือคำอธิบายประกอบ @runInSeparateProcess

หากคุณใช้ PHPUnit ~ 4.1 หรือบางอย่างและได้รับข้อผิดพลาด:

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

ลองเพิ่มสิ่งนี้ลงในไฟล์ bootstrap ของคุณเพื่อแก้ไข:

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

7
สิ่งนี้ทำให้เกิดข้อผิดพลาดในมีคำสั่ง define () บางคำสั่ง PHPUnit_Framework_Exception: หมายเหตุ: ค่าคงที่ xyz กำหนดไว้แล้ว
Minhaz

1
@mebjas นั่นฟังดูไม่เกี่ยว
SamHennessy

4
ฉันได้รับสิ่งนี้เมื่อสมัคร: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
นี่เป็นตัวเลือกที่ดีที่สุดในการแก้ปัญหาอย่างแน่นอน มันใช้งานได้อย่างมีเสน่ห์!
xarlymg89

2
ฉันต้องใช้ xdebug_get_headers () เพื่อรับอาร์เรย์ของส่วนหัวชุด headers_list () ฟังก์ชันส่วนกลางใช้ไม่ได้ในกรณีของฉัน
Shalom Sam

108

แม้ว่าการเรียกใช้การทดสอบในกระบวนการแยกต่างหากจะช่วยแก้ปัญหาได้ แต่ก็มีค่าใช้จ่ายที่เห็นได้ชัดเจนเมื่อเรียกใช้ชุดการทดสอบจำนวนมาก

การแก้ไขของฉันคือกำหนดเอาต์พุตของ phpunit ไปยัง stderr ดังนี้:

phpunit --stderr <options>

สิ่งนี้ควรแก้ไขปัญหาและยังหมายความว่าคุณไม่จำเป็นต้องสร้างฟังก์ชัน wrapper และแทนที่เหตุการณ์ทั้งหมดในโค้ดของคุณ


3
ยอดเยี่ยม! ไม่มีการเปลี่ยนแปลงรหัสของฉันไม่มีกระบวนการแยกต่างหากและใช้งานได้
alexfernandez

แต่ฉันจะยังคงเห็นข้อผิดพลาดที่ฉันทำและได้รับผลลัพธ์ของ error_reporting (E_ALL) หรือไม่
spankmaster79

1
@ spankmaster79 ใช่มันจะไปที่ข้อผิดพลาดมาตรฐานของเทอร์มินัลของคุณ ตามค่าเริ่มต้นเทอร์มินัลส่วนใหญ่จะพิมพ์ข้อผิดพลาดมาตรฐานและข้อผิดพลาดมาตรฐานพร้อมกัน แต่แท้จริงแล้วเป็นสตรีมแยก
Jon Cairns

ฉันถูกสร้างขึ้น !!! tnx สำหรับเคล็ดลับนี้มันสมเหตุสมผลควรรายงานข้อผิดพลาดไปยัง stderror !!! Tnx
th3n3

42
คุณสามารถเพิ่มstderr="true"phpunit.xml ของคุณเพื่อบันทึกการกดแป้นพิมพ์ไม่กี่ครั้ง
tszming

10

นอกจากนี้: สำหรับฉันheaders_list()ยังคงส่งคืนองค์ประกอบ 0 ฉันสังเกตเห็นความคิดเห็นของ@titelในคำถามและคิดว่าสมควรได้รับการกล่าวถึงเป็นพิเศษที่นี่:

แค่อยากจะพูดถึงเรื่องนี้หากมีคนอื่นสนใจเรื่องนี้เช่นกัน headers_list()ไม่ทำงานในขณะที่เรียกใช้ PHPunit (ซึ่งใช้ PHP CLI) แต่ใช้xdebug_get_headers()งานได้แทน

HTH


4

ดังที่ได้กล่าวไปแล้วในความคิดเห็นฉันคิดว่ามันเป็นทางออกที่ดีกว่าในการกำหนด processIsolation ในไฟล์กำหนดค่า XML เช่น

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

เช่นนี้คุณไม่ต้องผ่านตัวเลือก --stderr ซึ่งอาจทำให้เพื่อนร่วมงานของคุณระคายเคือง


4
อาจเป็นการดีกว่าที่จะกำหนดไว้สำหรับการทดสอบที่ต้องการเท่านั้น การตั้งค่าสำหรับการทดสอบทั้งหมดจะทำให้การทดสอบช้าลง
ชิ

3

ฉันมีวิธีแก้ปัญหาที่รุนแรงมากขึ้นเพื่อที่จะใช้$_SESSIONในไฟล์ที่ทดสอบ / รวมไว้ ฉันแก้ไขหนึ่งในPHPUnitไฟล์ที่../PHPUnit/Utils/Printer.phpจะมี"session_start();"ก่อนที่จะมีคำสั่ง "พิมพ์ $ บัฟเฟอร์"

มันได้ผลสำหรับฉันเหมือนมีเสน่ห์ แต่ฉันคิดว่าโซลูชันของผู้ใช้ "joonty" ดีที่สุดจนถึงตอนนี้


คุณส่งคำขอดึงข้อมูลในที่เก็บหรือไม่ ฉันเดาว่านี่อาจเป็นปัญหาทั่วไป
t1gor

ขอบคุณสำหรับคำใบ้ฉันเรียก session_start () ใน phpunit bootstrap.php ของฉันและมันใช้ได้กับฉัน
bumperbox

0

ทางเลือกอื่นสำหรับ @runInSeparateProcess คือการระบุอ็อพชัน --process-isolation เมื่อเรียกใช้ PHPUnit:

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

ซึ่งคล้ายคลึงกับการตั้งค่าอ็อพชัน processIsolation = "true" ใน phpunit.xml

โซลูชันนี้มีข้อดี / ข้อเสียที่คล้ายคลึงกันในการระบุอ็อพชัน --stderr ซึ่งไม่ได้ผลในกรณีของฉัน โดยทั่วไปไม่จำเป็นต้องมีการเปลี่ยนแปลงโค้ดแม้ว่าอาจจะมีผลกระทบด้านประสิทธิภาพเนื่องจากการรันการทดสอบแต่ละครั้งในกระบวนการ PHP แยก


นี่อาจเป็นขั้นตอนแรกในการติดตามสาเหตุและทำการทดสอบบางอย่าง แต่ในการสร้างการทดสอบที่ดูแลรักษาได้เพียงแค่ฝังคำอธิบายประกอบลงในไฟล์ทดสอบโดยตรง ยิ่งต้องการตัวเลือก 'พิเศษ' น้อยลงในบรรทัดคำสั่งการบำรุงรักษาการกำหนดค่าระบบ CI ก็ง่ายขึ้น
Shi

ใครเคยลดระดับ # ล้มเหลว คำตอบนี้ถูกต้องเป็นอีกทางเลือกหนึ่ง
fb

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.