PHPUnit ยืนยันว่ามีข้อผิดพลาดถูกโยน?


337

ไม่มีใครรู้ว่ามีassertหรือสิ่งที่สามารถทดสอบว่าข้อยกเว้นถูกโยนลงในรหัสการทดสอบหรือไม่


2
สำหรับคำตอบเหล่านั้น: สิ่งที่เกี่ยวกับการยืนยันที่หลากหลายในฟังก์ชั่นทดสอบและฉันแค่คาดว่าจะมีข้อยกเว้นการโยนหนึ่งครั้ง ฉันต้องแยกพวกมันออกและนำไปทดสอบฟังก์ชั่นอิสระหรือไม่?
Panwen Wang

คำตอบ:


549
<?php
require_once 'PHPUnit/Framework.php';

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        $this->expectException(InvalidArgumentException::class);
        // or for PHPUnit < 5.2
        // $this->setExpectedException(InvalidArgumentException::class);

        //...and then add your test code that generates the exception 
        exampleMethod($anInvalidArgument);
    }
}

expectException () เอกสาร PHPUnit

บทความผู้เขียน PHPUnitให้คำอธิบายโดยละเอียดเกี่ยวกับการทดสอบข้อยกเว้นวิธีปฏิบัติที่ดีที่สุด


8
หากคุณใช้เนมสเปซคุณต้องป้อนเนมสเปซเต็มรูปแบบ:$this->setExpectedException('\My\Name\Space\MyCustomException');
Alcalyn

15
ความจริงที่ว่าคุณไม่สามารถกำหนดรหัสที่แม่นยำซึ่งคาดว่าจะเกิดขึ้นเป็นข้อผิดพลาด IMO และการไร้ความสามารถในการทดสอบมากกว่าหนึ่งข้อยกเว้นในการทดสอบเดียวกันทำให้การทดสอบสำหรับข้อยกเว้นที่คาดหวังจำนวนมากเป็นเรื่องที่น่ารังเกียจ ฉันเขียนคำยืนยันจริงเพื่อพยายามแก้ไขปัญหาเหล่านั้น
mindplay.dk

18
FYI: ณวิธีphpunit 5.2.0 setExpectedExceptionเลิกใช้แล้วแทนที่ด้วยวิธีexpectExceptionหนึ่ง :)
hejdav

41
อะไรที่จะไม่กล่าวถึงในเอกสารหรือนี่ แต่รหัสที่คาดว่าจะโยนความต้องการข้อยกเว้นที่จะเรียกว่าหลังจากที่ expectException()ในขณะที่บางคนอาจจะเห็นได้ชัด แต่ก็เป็นgotchaสำหรับฉัน
Jason McCreary

7
ไม่ชัดเจนจากเอกสาร แต่ไม่มีโค้ดใด ๆ หลังจากฟังก์ชั่นของคุณที่มีข้อผิดพลาดเกิดขึ้น ดังนั้นหากคุณต้องการทดสอบข้อยกเว้นหลายอย่างในกรณีทดสอบเดียวกันคุณไม่สามารถทำได้
เรนท์

122

คุณยังสามารถใช้คำอธิบายประกอบ docblockจนกว่าจะวางจำหน่าย PHPUnit 9:

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException InvalidArgumentException
     */
    public function testException()
    {
        ...
    }
}

สำหรับ PHP 5.5+ (โดยเฉพาะกับโค้ดเนมสเปซ) ตอนนี้ฉันชอบใช้ ::class


3
IMO นี่เป็นวิธีที่ต้องการ
Mike Purcell

12
@LeviMorrison - IMHO ไม่ควรทดสอบข้อความแสดงข้อยกเว้นเช่นเดียวกับข้อความบันทึก ทั้งสองได้รับการพิจารณาภายนอกข้อมูลที่เป็นประโยชน์เมื่อดำเนินคู่มือนิติ จุดสำคัญในการทดสอบคือประเภทของข้อยกเว้น อะไรก็ตามที่เกินกว่าที่จะผูกมัดไว้แน่นเกินไปกับการนำไปใช้งาน IncorrectPasswordExceptionควรจะเพียงพอ - ว่าข้อความเท่ากับ"Wrong password for bob@me.com"เสริม เพิ่มไปที่คุณต้องการใช้เวลาน้อยที่สุดในการเขียนแบบทดสอบและคุณเริ่มเห็นว่าการทดสอบแบบง่ายมีความสำคัญเพียงใด
David Harkness

5
@ DavidHarkness ฉันคิดว่ามีใครบางคนที่จะนำมาขึ้นที่ ในทำนองเดียวกันฉันจะยอมรับว่าข้อความทดสอบโดยทั่วไปเข้มงวดและแน่นเกินไป อย่างไรก็ตามมันเป็นความเข้มงวดและการผูกมัดอย่างแน่นหนาที่อาจ (เน้นโดยเจตนา) เป็นสิ่งที่ต้องการในบางสถานการณ์เช่นการบังคับใช้ข้อกำหนด
Levi Morrison

1
ฉันจะไม่ดูใน doc-block เพื่อทำความเข้าใจกับสิ่งที่คาดหวัง แต่ฉันจะดูรหัสทดสอบจริง (ไม่ว่าจะเป็นการทดสอบประเภทใด) นั่นเป็นมาตรฐานสำหรับการทดสอบอื่น ๆ ทั้งหมด; ฉันไม่เห็นเหตุผลที่ถูกต้องสำหรับข้อยกเว้นที่จะ (โอ้พระเจ้า) เป็นข้อยกเว้นสำหรับอนุสัญญานี้
Kamafeather

3
กฎ "ไม่ทดสอบข้อความ" จะใช้งานได้เว้นแต่ว่าคุณจะทดสอบวิธีการที่มีการยกเว้นประเภทเดียวกันในหลายส่วนของรหัสโดยมีความแตกต่างเพียงอย่างเดียวคือรหัสข้อผิดพลาดซึ่งส่งผ่านในข้อความ ระบบของคุณอาจแสดงข้อความถึงผู้ใช้ตามข้อความข้อยกเว้น (ไม่ใช่ประเภทข้อยกเว้น) ในกรณีนั้นมันไม่สำคัญว่าข้อความใดที่ผู้ใช้เห็นดังนั้นคุณควรทดสอบข้อความแสดงข้อผิดพลาด
Vanja D.

34

หากคุณกำลังทำงานบน PHP 5.5+ คุณสามารถใช้::classความละเอียดที่จะได้รับชื่อของคลาสที่มี/expectException setExpectedExceptionสิ่งนี้ให้ประโยชน์หลายประการ:

  • ชื่อจะมีคุณสมบัติครบถ้วนพร้อมกับเนมสเปซ (ถ้ามี)
  • มันแก้ไขเป็นstringดังนั้นมันจะทำงานกับ PHPUnit รุ่นใดก็ได้
  • คุณได้รับโค้ดที่สมบูรณ์ใน IDE ของคุณ
  • คอมไพเลอร์ PHP จะส่งข้อผิดพลาดหากคุณพิมพ์ชื่อคลาสผิด

ตัวอย่าง:

namespace \My\Cool\Package;

class AuthTest extends \PHPUnit_Framework_TestCase
{
    public function testLoginFailsForWrongPassword()
    {
        $this->expectException(WrongPasswordException::class);
        Auth::login('Bob', 'wrong');
    }
}

คอมไพล์ PHP

WrongPasswordException::class

เข้าไป

"\My\Cool\Package\WrongPasswordException"

ไม่มี PHPUnit เป็นคนฉลาด

หมายเหตุ : PHPUnit 5.2 แนะนำ แทนสำหรับexpectExceptionsetExpectedException


32

รหัสด้านล่างจะทดสอบข้อความข้อยกเว้นและรหัสข้อยกเว้น

สำคัญ:มันจะล้มเหลวหากข้อยกเว้นที่คาดไว้ไม่เกิดขึ้นเช่นกัน

try{
    $test->methodWhichWillThrowException();//if this method not throw exception it must be fail too.
    $this->fail("Expected exception 1162011 not thrown");
}catch(MySpecificException $e){ //Not catching a generic Exception or the fail function is also catched
    $this->assertEquals(1162011, $e->getCode());
    $this->assertEquals("Exception Message", $e->getMessage());
}

6
$this->fail()ไม่ได้มีไว้เพื่อใช้ในแบบที่ฉันไม่คิดอย่างน้อยก็ในปัจจุบัน (PHPUnit 3.6.11); มันทำหน้าที่เป็นข้อยกเว้นตัวเอง โดยใช้ตัวอย่างของคุณถ้า$this->fail("Expected exception not thrown")จะเรียกว่าแล้วcatchบล็อกจะถูกเรียกและ$e->getMessage()เป็น"ที่คาดว่าจะมีข้อยกเว้นไม่โยน"
เคน

1
@ken คุณอาจพูดถูก เรียกร้องให้failอาจจะเป็นหลังจากที่จับบล็อกไม่ได้อยู่ในลอง
Frank Farmer

1
ฉันต้อง downvote เพราะการโทรไปfailไม่ควรอยู่ในtryบล็อค ในตัวมันเองเรียกcatchบล็อกที่ให้ผลลัพธ์ที่ผิดพลาด
Twifty

6
catch(Exception $e)ผมเชื่อว่าเหตุผลนี้ไม่ได้ทำงานได้ดีเป็นสถานการณ์บางอย่างที่มันจับข้อยกเว้นทั้งหมดที่มี วิธีนี้ได้ผลค่อนข้างดีสำหรับฉันเมื่อฉันพยายามที่จะจับข้อยกเว้นเฉพาะ:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
spyle

23

คุณสามารถใช้ส่วนขยาย assertExceptionเพื่อยืนยันข้อยกเว้นมากกว่าหนึ่งข้อระหว่างการทดสอบหนึ่งครั้ง

แทรกวิธีการลงใน TestCase ของคุณและใช้:

public function testSomething()
{
    $test = function() {
        // some code that has to throw an exception
    };
    $this->assertException( $test, 'InvalidArgumentException', 100, 'expected message' );
}

ฉันยังสร้างนิสัยให้กับคนรักของรหัสที่ดี ..


คุณกำลังใช้ PHPUnit ใดอยู่ ฉันใช้ PHPUnit 4.7.5 และassertExceptionไม่มีการกำหนดไว้ ฉันไม่พบมันในคู่มือ PHPUnit
ทางกายภาพ

2
asertExceptionวิธีการไม่ได้เป็นส่วนหนึ่งของ PHPUnit เดิม คุณต้องรับPHPUnit_Framework_TestCaseคลาสและเพิ่มวิธีการเชื่อมโยงในโพสต์ด้านบนด้วยตนเอง กรณีทดสอบของคุณจะสืบทอดคลาสที่สืบทอดนี้
hejdav

18

ทางเลือกอื่นสามารถเป็นดังนี้:

$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Expected Exception Message');

\PHPUnit_Framework_TestCaseโปรดตรวจสอบว่าขอบเขตระดับการทดสอบของคุณ


เพื่อให้แน่ใจว่าน้ำตาลมากที่สุดในไวยากรณ์นี้
AndrewMcLagan

13

expectExceptionวิธีการPHPUnit ไม่สะดวกมากเนื่องจากช่วยให้สามารถทดสอบข้อยกเว้นเดียวเท่านั้นสำหรับวิธีทดสอบ

ฉันได้ทำฟังก์ชันผู้ช่วยนี้เพื่อยืนยันว่าบางฟังก์ชันมีข้อยกเว้น:

/**
 * Asserts that the given callback throws the given exception.
 *
 * @param string $expectClass The name of the expected exception class
 * @param callable $callback A callback which should throw the exception
 */
protected function assertException(string $expectClass, callable $callback)
{
    try {
        $callback();
    } catch (\Throwable $exception) {
        $this->assertInstanceOf($expectClass, $exception, 'An invalid exception was thrown');
        return;
    }

    $this->fail('No exception was thrown');
}

เพิ่มไปยังคลาสทดสอบของคุณและเรียกวิธีนี้:

public function testSomething() {
    $this->assertException(\PDOException::class, function() {
        new \PDO('bad:param');
    });
    $this->assertException(\PDOException::class, function() {
        new \PDO('foo:bar');
    });
}

ทางออกที่ดีที่สุดแน่นอนจากคำตอบทั้งหมด! โยนมันเข้าไปในลักษณะและบรรจุภัณฑ์!
domdambrogia

11

โซลูชันที่ครอบคลุม

" แนวปฏิบัติที่ดีที่สุด " ของ PHPUnit ในปัจจุบันสำหรับการทดสอบข้อยกเว้นดูเหมือนว่า ... น่าเบื่อ ( เอกสาร )

เนื่องจากฉันต้องการมากกว่าexpectExceptionการนำไปใช้ในปัจจุบันฉันจึงสร้างลักษณะที่จะใช้ในกรณีทดสอบของฉัน มันเป็นรหัส~ 50 บรรทัดเท่านั้น

  • รองรับข้อยกเว้นหลายรายการต่อการทดสอบ
  • รองรับการยืนยันที่เรียกว่าหลังจากมีการโยนข้อยกเว้น
  • ตัวอย่างการใช้งานที่ทนทานและชัดเจน
  • มาตรฐาน assertไวยากรณ์
  • รองรับการยืนยันมากกว่าข้อความรหัสและคลาส
  • รองรับการยืนยันผกผัน assertNotThrows
  • รองรับThrowableข้อผิดพลาดPHP 7

ห้องสมุด

ฉันเผยแพร่AssertThrowsคุณลักษณะไปที่ Github และผู้บรรจุหีบห่อเพื่อให้สามารถติดตั้งกับผู้แต่งได้

ตัวอย่างง่ายๆ

เพียงเพื่อแสดงให้เห็นถึงจิตวิญญาณที่อยู่เบื้องหลังไวยากรณ์:

<?php

// Using simple callback
$this->assertThrows(MyException::class, [$obj, 'doSomethingBad']);

// Using anonymous function
$this->assertThrows(MyException::class, function() use ($obj) {
    $obj->doSomethingBad();
});

สวยเนี๊ยบ?


ตัวอย่างการใช้งานแบบเต็ม

โปรดดูตัวอย่างด้านล่างสำหรับการใช้งานที่ครอบคลุมมากขึ้น:

<?php

declare(strict_types=1);

use Jchook\AssertThrows\AssertThrows;
use PHPUnit\Framework\TestCase;

// These are just for illustration
use MyNamespace\MyException;
use MyNamespace\MyObject;

final class MyTest extends TestCase
{
    use AssertThrows; // <--- adds the assertThrows method

    public function testMyObject()
    {
        $obj = new MyObject();

        // Test a basic exception is thrown
        $this->assertThrows(MyException::class, function() use ($obj) {
            $obj->doSomethingBad();
        });

        // Test custom aspects of a custom extension class
        $this->assertThrows(MyException::class, 
            function() use ($obj) {
                $obj->doSomethingBad();
            },
            function($exception) {
                $this->assertEquals('Expected value', $exception->getCustomThing());
                $this->assertEquals(123, $exception->getCode());
            }
        );

        // Test that a specific exception is NOT thrown
        $this->assertNotThrows(MyException::class, function() use ($obj) {
            $obj->doSomethingGood();
        });
    }
}

?>

4
แดกดันเล็กน้อยที่แพ็คเกจของคุณสำหรับการทดสอบหน่วยไม่มีการทดสอบหน่วยใน repo
domdambrogia

2
@domdambrogia ต้องขอบคุณ@ jean-beguinตอนนี้มีการทดสอบหน่วยแล้ว
jchook

8
public function testException() {
    try {
        $this->methodThatThrowsException();
        $this->fail("Expected Exception has not been raised.");
    } catch (Exception $ex) {
        $this->assertEquals($ex->getMessage(), "Exception message");
    }

}

ลายเซ็นของassertEquals()คือassertEquals(mixed $expected, mixed $actual...)ย้อนกลับในตัวอย่างของคุณดังนั้นมันควรจะเป็น$this->assertEquals("Exception message", $ex->getMessage());
Roger Campanera

7

นี่คือข้อยกเว้นทั้งหมดที่คุณสามารถทำได้ โปรดทราบว่าทั้งหมดของพวกเขาไม่จำเป็น

class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        // make your exception assertions
        $this->expectException(InvalidArgumentException::class);
        // if you use namespaces:
        // $this->expectException('\Namespace\MyExceptio‌​n');
        $this->expectExceptionMessage('message');
        $this->expectExceptionMessageRegExp('/essage$/');
        $this->expectExceptionCode(123);
        // code that throws an exception
        throw new InvalidArgumentException('message', 123);
   }

   public function testAnotherException()
   {
        // repeat as needed
        $this->expectException(Exception::class);
        throw new Exception('Oh no!');
    }
}

เอกสารที่สามารถพบได้ที่นี่


มันไม่ถูกต้องเนื่องจาก PHP หยุดข้อยกเว้นครั้งแรก PHPUnit ตรวจสอบว่าข้อยกเว้นที่ส่งออกมานั้นมีประเภทที่ถูกต้องและบอกว่า«การทดสอบเป็นปกติ»มันไม่รู้ด้วยซ้ำเกี่ยวกับข้อยกเว้นที่สอง
กลเม็ดเด็ดพราย

3
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

ระวังให้มาก"/**"สังเกตคู่ "*" การเขียนเฉพาะ "**" (เครื่องหมายดอกจัน) จะทำให้รหัสของคุณล้มเหลว ตรวจสอบให้แน่ใจว่าคุณใช้ phpUnit เวอร์ชันล่าสุดด้วย ในรุ่นก่อนหน้าของ phpunit @ ที่ไม่คาดคิดข้อยกเว้นไม่ได้รับการสนับสนุน ฉันมี 4.0 และมันไม่ทำงานสำหรับฉันฉันต้องอัปเดตเป็น 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composerเพื่ออัปเดตด้วยผู้แต่ง


0

สำหรับ PHPUnit 5.7.27 และ PHP 5.6 และเพื่อทดสอบข้อยกเว้นหลายรายการในการทดสอบครั้งเดียวสิ่งสำคัญคือการบังคับให้ทำการทดสอบข้อยกเว้น การใช้การจัดการข้อยกเว้นเพียงอย่างเดียวเพื่อยืนยันอินสแตนซ์ของข้อยกเว้นจะข้ามการทดสอบสถานการณ์หากไม่มีข้อยกเว้นเกิดขึ้น

public function testSomeFunction() {

    $e=null;
    $targetClassObj= new TargetClass();
    try {
        $targetClassObj->doSomething();
    } catch ( \Exception $e ) {
    }
    $this->assertInstanceOf(\Exception::class,$e);
    $this->assertEquals('Some message',$e->getMessage());

    $e=null;
    try {
        $targetClassObj->doSomethingElse();
    } catch ( Exception $e ) {
    }
    $this->assertInstanceOf(\Exception::class,$e);
    $this->assertEquals('Another message',$e->getMessage());

}

0
function yourfunction($a,$z){
   if($a<$z){ throw new <YOUR_EXCEPTION>; }
}

นี่คือการทดสอบ

class FunctionTest extends \PHPUnit_Framework_TestCase{

   public function testException(){

      $this->setExpectedException(<YOUR_EXCEPTION>::class);
      yourfunction(1,2);//add vars that cause the exception 

   }

}

0

PhpUnit เป็นห้องสมุดที่น่าทึ่ง แต่ประเด็นนี้ค่อนข้างน่าผิดหวัง นี่คือเหตุผลที่เราสามารถใช้ไลบรารี opensource turbotesting-php ซึ่งมีวิธีการยืนยันที่สะดวกมากเพื่อช่วยเราทดสอบข้อยกเว้น พบได้ที่นี่:

https://github.com/edertone/TurboTesting/blob/master/TurboTesting-Php/src/main/php/utils/AssertUtils.php

และเพื่อใช้งานเราจะทำดังต่อไปนี้:

AssertUtils::throwsException(function(){

    // Some code that must throw an exception here

}, '/expected error message/');

หากรหัสที่เราพิมพ์ภายในฟังก์ชั่นที่ไม่ระบุชื่อไม่ได้เกิดข้อยกเว้นข้อยกเว้นจะถูกโยนทิ้ง

หากรหัสที่เราพิมพ์ภายในฟังก์ชั่นที่ไม่ระบุชื่อเกิดข้อยกเว้น แต่ข้อความไม่ตรงกับ regexp ที่คาดไว้จะมีการโยนข้อยกเว้นด้วย

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