ChatGPT解决这个技术问题 Extra ChatGPT

PHPUnit 断言抛出异常?

有谁知道是否有 assert 或类似的东西可以测试在被测试的代码中是否引发了异常?

对于这些答案:测试函数中的多断言怎么样,我只希望有一个抛出异常?我是否必须将它们分开并将其放在独立的测试功能中?
@PanwenWang 要测试多个异常或来自异常的 getter 的多个返回,请参阅 this answer

J
Josh Kelley
<?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 documentation

PHPUnit author article 提供了有关测试异常最佳做法的详细说明。


如果您使用命名空间,那么您需要输入完整的命名空间:$this->setExpectedException('\My\Name\Space\MyCustomException');
您无法指定预期抛出的精确代码行这一事实是 IMO 错误。并且无法在同一个测试中测试多个异常,这使得测试许多预期的异常成为一件非常笨拙的事情。我写了一个 actual assertion 来尝试解决这些问题。
仅供参考:从 phpunit 5.2.0 开始,setExpectedException 方法已弃用,替换为 expectException 方法。 :)
文档或此处未提及的内容,但预计会引发异常的代码需要在 expectException() 之后调用。虽然这对某些人来说可能很明显,但对我来说这是一个陷阱
从文档中看并不明显,但是在您的函数引发异常之后不会执行任何代码。因此,如果您想在同一个测试用例中测试多个异常,则不能。
D
David Harkness

在 PHPUnit 9 发布之前,您还可以使用 docblock annotation

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

对于 PHP 5.5+(尤其是命名空间代码),我现在更喜欢使用 ::class


IMO,这是首选方法。
@LeviMorrison - 恕我直言,不应测试异常 message ,类似于日志消息。在执行手动取证时,两者都被视为无关紧要的有用信息。测试的关键点是异常的type。除此之外的任何东西都与实现绑定得太紧。 IncorrectPasswordException 应该足够了 - 等于 "Wrong password for bob@me.com" 的消息是辅助的。再加上您希望花尽可能少的时间编写测试,您就会开始看到简单测试变得多么重要。
@DavidHarkness 我想有人会提出来。同样,我同意测试消息通常过于严格和严格。然而,在某些情况下(例如强制执行规范),可能(有目的地强调)需要这种严格性和紧密绑定。
我不会在文档块中观看以了解它的预期,但我会查看实际的测试代码(无论测试类型如何)。这是所有其他测试的标准;我看不出 Exceptions 是(天哪)这个约定的例外的正当理由。
“不要测试消息”规则听起来是有效的,除非您测试一个在多个代码部分中抛出相同异常类型的方法,唯一的区别是错误 ID,它是在消息中传递的。您的系统可能会根据异常消息(不是异常类型)向用户显示消息。在这种情况下,用户看到的消息并不重要,因此,您应该测试错误消息。
A
Amit Ray

另一种方法可以如下:

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

请确保您的测试类范围为 \PHPUnit_Framework_TestCase


肯定是这个语法中最糖的
似乎 expectExceptionMessage 表现为正则表达式。如果您的错误消息是“Foo bar Baz”,$this->expectExceptionMessage('Foo'); 将使测试通过。
这是一个!另外,expectExceptionCode(401)
只要实际的异常消息包含指定的消息,@LucasBustamante expectExceptionMessage($message) 就会起作用。 PHPUnit 在内部使用 strpos 来评估条件。
D
David Harkness

如果您在 PHP 5.5+ 上运行,则可以使用 ::class resolution 通过 expectException/setExpectedException 获取类的名称。这提供了几个好处:

该名称将使用其名称空间(如果有)完全限定。

它解析为一个字符串,因此它适用于任何版本的 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 引入了 expectException 作为 setExpectedException 的替代品。


F
Farid Movsumov

下面的代码将测试异常消息和异常代码。

重要提示:如果没有抛出预期的异常,它将失败。

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

$this->fail() 不应该以这种方式使用我不认为,至少目前不是(PHPUnit 3.6.11);它本身就是一个例外。使用您的示例,如果调用 $this->fail("Expected exception not thrown"),则触发 catch 块并且 $e->getMessage()“未引发预期异常”
@ken你可能是对的。对 fail 的调用可能属于 catch 块之后,而不是在 try 内。
我必须投反对票,因为对 fail 的调用不应该在 try 块中。它本身会触发 catch 块产生错误结果。
我相信这不起作用的原因是某些情况是它使用 catch(Exception $e) 捕获所有异常。当我尝试捕获特定异常时,此方法对我来说效果很好:try { throw new MySpecificException; $this->fail('MySpecificException not thrown'); } catch(MySpecificException $e){}
h
hejdav

您可以使用 assertException extension 在一次测试执行期间断言多个异常。

将方法插入您的 TestCase 并使用:

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

我还为漂亮代码的爱好者制作了一个 trait


您使用的是哪个 PHPUnit?我使用的是 PHPUnit 4.7.5,但没有定义 assertException。我也无法在 PHPUnit 手册中找到它。
asertException 方法不是原始 PHPUnit 的一部分。您必须继承 PHPUnit_Framework_TestCase 类并手动添加 method linked in post above。然后,您的测试用例将继承这个继承的类。
这很棒。我断言我对两个异常的期望,而 phpunit 的输出只显示了一个断言。
J
Jimmix

TLDR;滚动到:使用 PHPUnit 的数据提供者

PHPUnit 9.5 提供以下方法来测试异常:

$this->expectException(string $exceptionClassName);
$this->expectExceptionCode(int|string $code);
$this->expectExceptionMessage(string $message);
$this->expectExceptionMessageMatches(string $regularExpression);
$this->expectExceptionObject(\Exception $exceptionObject);

但是,Documentation 对测试代码中上述任何方法的顺序含糊不清。

如果您习惯使用断言,例如:

<?php

class SimpleAssertionTest extends \PHPUnit\Framework\TestCase
{
    public function testSimpleAssertion(): void
    {
        $expected = 'bar';
        $actual = 'bar';
        $this->assertSame($expected, $actual);
    }
}

输出:

 ✔ Simple assertion
OK (1 test, 1 assertion)

您可能会对异常测试失败感到惊讶:

<?php

use PHPUnit\Framework\TestCase;

final class ExceptionTest extends TestCase
{
    public function testException(): void
    {
        throw new \InvalidArgumentException();
        $this->expectException(\InvalidArgumentException::class);
    }
}

输出:

 ✘ Exception
   ├ InvalidArgumentException:

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

错误是因为:

一旦抛出异常,PHP 就不能返回到抛出异常的行之后的代码行。捕获异常在这方面没有任何改变。抛出异常是单程票。

与错误不同,异常没有能力从它们中恢复并使 PHP 继续执行代码,就好像根本没有异常一样。

因此 PHPUnit 甚至没有到达这个地方:

$this->expectException(\InvalidArgumentException::class);

如果前面有:

throw new \InvalidArgumentException();

而且,PHPUnit 永远无法到达那个地方,无论它的异常捕获能力如何。

因此使用任何 PHPUnit 的异常测试方法:

$this->expectException(string $exceptionClassName);
$this->expectExceptionCode(int|string $code);
$this->expectExceptionMessage(string $message);
$this->expectExceptionMessageMatches(string $regularExpression);
$this->expectExceptionObject(\Exception $exceptionObject);

必须在预期会引发异常的代码之前,这与在设置实际值之后放置的断言相反。

使用异常测试的正确顺序:

<?php

use PHPUnit\Framework\TestCase;

final class ExceptionTest extends TestCase
{
    public function testException(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        throw new \InvalidArgumentException();
    }
}

因为必须在抛出异常之前调用 PHPUnit 内部方法来测试异常,所以与测试异常相关的 PHPUnit 方法从 $this->excpect 而不是 $this->assert 开始是有意义的。

已经知道:

一旦抛出异常,PHP 就不能返回到抛出异常的行之后的代码行。

您应该能够轻松地发现此测试中的错误:

<?php
namespace VendorName\PackageName;

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testThrowException(): void
    {
        # Should be OK
        $this->expectException(\RuntimeException::class);
        throw new \RuntimeException();

        # Should Fail
        $this->expectException(\RuntimeException::class);
        throw new \InvalidArgumentException();
    }
}

第一个 $this->expectException() 应该没问题,它期望在抛出预期的确切异常类之前有一个异常类,所以这里没有错。

应该失败的第二个在抛出一个完全不同的异常之前需要 RuntimeException 类,所以它应该失败但是 PHPUnit 执行会到达那个地方吗?

测试的输出是:

 ✔ Throw exception

OK (1 test, 1 assertion)

OK

不,如果测试通过,它离 OK 还很远,它应该在第二个异常上 Fail。这是为什么?

请注意,输出具有:

好的(1 个测试,1 个断言)

其中测试计数正确但只有 1 assertion

应该有 2 个断言 = OKFail 使测试不通过。

这仅仅是因为 PHPUnit 在行后执行 testThrowException 完成:

throw new \RuntimeException();

这是 testThrowException 范围之外的单程票,到达 PHPUnit 捕获 \RuntimeException 并执行它需要做的事情的地方,但无论它可以做什么,我们都知道它无法跳回 testThrowException因此代码:

# Should Fail
$this->expectException(\RuntimeException::class);
throw new \InvalidArgumentException();

永远不会被执行,这就是为什么从 PHPUnit 的角度来看测试结果是 OK 而不是 Fail

如果您想在同一测试方法中使用多个 $this->expectException() 或混合使用 $this->expectException()$this->expectExceptionMessage() 调用,这不是一个好消息:

<?php
namespace VendorName\PackageName;

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testThrowException(): void
    {
        # OK
        $this->expectException(\RuntimeException::class);
        throw new \RuntimeException('Something went wrong');

        # Fail
        $this->expectExceptionMessage('This code will never be executed');
        throw new \RuntimeException('Something went wrong');
    }
}

给出错误:

好的(1 个测试,1 个断言)

因为一旦抛出异常,与测试异常相关的所有其他 $this->expect... 调用都不会执行,PHPUnit 测试用例结果将仅包含第一个预期异常的结果。

如何测试多个异常?

将多个异常拆分为单独的测试:

<?php
namespace VendorName\PackageName;

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testThrowExceptionBar(): void
    {
        # OK
        $this->expectException(\RuntimeException::class);
        throw new \RuntimeException();
    }

    public function testThrowExceptionFoo(): void
    {
        # Fail
        $this->expectException(\RuntimeException::class);
        throw new \InvalidArgumentException();
    }
}

给出:

 ✔ Throw exception bar
 ✘ Throw exception foo
   ┐
   ├ Failed asserting that exception of type "InvalidArgumentException" matches expected exception "RuntimeException". Message was: "" at

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

FAILURES 应如此。

然而,这种方法在它的基本方法上有一个缺点——对于每个抛出的异常,你都需要一个单独的测试。这将产生大量的测试来检查异常。

捕获异常并用断言检查它

如果您在抛出异常后无法继续执行脚本,您可以简单地捕获一个预期的异常,然后使用异常提供的方法获取有关它的所有数据,并将其与预期值和断言的组合使用:

<?php
namespace VendorName\PackageName;

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testThrowException(): void
    {
        # OK
        unset($className);
        try {
            $location = __FILE__ . ':' . (string) (__LINE__ + 1);
            throw new \RuntimeException('Something went wrong'); 

        } catch (\Exception $e) {
            $className = get_class($e);
            $msg = $e->getMessage();
            $code = $e->getCode();
        }

        $expectedClass = \RuntimeException::class;
        $expectedMsg = 'Something went wrong';
        $expectedCode = 0;

        if (empty($className)) {
            $failMsg = 'Exception: ' . $expectedClass;
            $failMsg .= ' with msg: ' . $expectedMsg;
            $failMsg .= ' and code: ' . $expectedCode;
            $failMsg .= ' at: ' . $location;
            $failMsg .= ' Not Thrown!';
            $this->fail($failMsg);
        }

        $this->assertSame($expectedClass, $className);
        $this->assertSame($expectedMsg, $msg);
        $this->assertSame($expectedCode, $code);

        # ------------------------------------------

        # Fail
        unset($className);
        try {
            $location = __FILE__ . ':' . (string) (__LINE__ + 1);
            throw new \InvalidArgumentException('I MUST FAIL !'); 

        } catch (\Exception $e) {
            $className = get_class($e);
            $msg = $e->getMessage();
            $code = $e->getCode();
        }

        $expectedClass = \InvalidArgumentException::class;
        $expectedMsg = 'Something went wrong';
        $expectedCode = 0;

        if (empty($className)) {
            $failMsg = 'Exception: ' . $expectedClass;
            $failMsg .= ' with msg: ' . $expectedMsg;
            $failMsg .= ' and code: ' . $expectedCode;
            $failMsg .= ' at: ' . $location;
            $failMsg .= ' Not Thrown!';
            $this->fail($failMsg);
        }

        $this->assertSame($expectedClass, $className);
        $this->assertSame($expectedMsg, $msg);
        $this->assertSame($expectedCode, $code);
    }
}

给出:

 ✘ Throw exception
   ┐
   ├ Failed asserting that two strings are identical.
   ┊ ---·Expected
   ┊ +++·Actual
   ┊ @@ @@
   ┊ -'Something·went·wrong'
   ┊ +'I·MUST·FAIL·!'

FAILURES!
Tests: 1, Assertions: 5, Failures: 1.

FAILURES 应该如此,但我的主啊,您是否阅读了以上所有内容?您需要注意清除变量 unset($className); 以检测是否引发了异常,然后此生物 $location = __FILE__ ... 以在未引发异常的情况下获得异常的精确位置,然后检查是否引发了异常 if (empty($className)) { ... }并使用 $this->fail($failMsg); 表示是否未引发异常。

使用 PHPUnit 的数据提供者

PHPUnit 有一个有用的机制,称为 Data Provider。数据提供者是一种返回带有数据集的数据(数组)的方法。当 PHPUnit 调用测试方法 - testThrowException 时,单个数据集用作参数。

如果数据提供者返回多个数据集,则测试方法将运行多次,每次都使用另一个数据集。这在测试多个异常或/和多个异常的属性(如类名、消息、代码)时很有帮助,因为即使:

一旦抛出异常,PHP 就不能返回到抛出异常的行之后的代码行。

PHPUnit 将多次运行测试方法,每次都使用不同的数据集,因此而不是在单个测试方法运行中测试多个异常(这将失败)。

这就是为什么我们可以让一个测试方法同时负责测试一个异常,但使用 PHPUnit 的数据提供程序使用不同的输入数据和预期的异常多次运行该测试方法。

数据提供者方法的定义可以通过对应该由数据提供者与数据集提供的测试方法进行 @dataProvider 注释来完成。

<?php

class ExceptionCheck
{
    public function throwE($data)
    {
        if ($data === 1) {
            throw new \RuntimeException;
        } else {
            throw new \InvalidArgumentException;
        }
    }
}

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function ExceptionTestProvider() : array
    {
        $data = [
            \RuntimeException::class =>
            [
                [
                    'input' => 1,
                    'className' => \RuntimeException::class
                ]
            ],

            \InvalidArgumentException::class =>
            [
                [
                    'input' => 2,
                    'className' => \InvalidArgumentException::class
                ]
            ]
        ];
        return $data;
    }

    /**
     * @dataProvider ExceptionTestProvider
     */
    public function testThrowException($data): void
    {
        $this->expectException($data['className']);
        $exceptionCheck = new ExceptionCheck;

        $exceptionCheck->throwE($data['input']);
    }
}

给出结果:

 ✔ Throw exception with RuntimeException
 ✔ Throw exception with InvalidArgumentException

OK (2 tests, 2 assertions)

请注意,即使整个 ExceptionTest 中只有一个测试方法,PHPUnit 的输出也是:

好的(2 个测试,2 个断言)

所以即使是这条线:

$exceptionCheck->throwE($data['input']);

第一次抛出异常,使用相同的测试方法测试另一个异常没有问题,因为 PHPUnit 再次使用不同的数据集运行它,这要归功于数据提供者。

数据提供者返回的每个数据集都可以命名,您只需使用一个字符串作为存储数据集的键。因此预期的异常类名被使用了两次。作为数据集数组的键和值(在“类名”键下),稍后用作 $this->expectException() 的参数。

使用字符串作为数据集的键名可以做出漂亮且不言自明的总结:

✔ 使用 RuntimeException 引发异常 ✔ 使用 InvalidArgumentException 引发异常

如果您更改线路:

if ($data === 1) {

至:

if ($data !== 1) {

public function throwE($data)

要抛出错误的异常并再次运行 PHPUnit,您将看到:

 ✘ Throw exception with RuntimeException
   ├ Failed asserting that exception of type "InvalidArgumentException" matches expected exception "RuntimeException". Message was: "" at (...)

 ✘ Throw exception with InvalidArgumentException
   ├ Failed asserting that exception of type "RuntimeException" matches expected exception "InvalidArgumentException". Message was: "" at (...)

FAILURES!
Tests: 2, Assertions: 2, Failures: 2.

正如预期的那样:

失败!测试:2,断言:2,失败:2。

准确指出导致一些问题的数据集名称:

✘ 使用 RuntimeException 引发异常 ✘ 使用 InvalidArgumentException 引发异常

使 public function throwE($data) 不引发任何异常:

public function throwE($data)
{
}

并再次运行 PHPUnit 给出:

 ✘ Throw exception with RuntimeException
   ├ Failed asserting that exception of type "RuntimeException" is thrown.

 ✘ Throw exception with InvalidArgumentException
   ├ Failed asserting that exception of type "InvalidArgumentException" is thrown.

FAILURES!
Tests: 2, Assertions: 2, Failures: 2.

看起来使用数据提供者有几个优点:

输入数据和/或预期数据与实际测试方法分开。每个数据集都可以有一个描述性的名称,清楚地指出是什么数据集导致测试通过或失败。如果测试失败,您会收到一条正确的失败消息,指出没有抛出异常或抛出了错误的异常,而不是断言 x 不是 y。测试可能引发多个异常的单个方法只需要一个测试方法。可以测试多个异常和/或多个异常的属性,如类名、消息、代码。不需要像 try catch 块这样的非必要代码,而只需使用 PHPUnit 的内置功能。

测试异常陷阱

“TypeError”类型的异常

使用 PHP7 数据类型支持此测试:

<?php
declare(strict_types=1);

class DatatypeChat
{
    public function say(string $msg)
    {
        if (!is_string($msg)) {
            throw new \InvalidArgumentException('Message must be a string');
        }
        return "Hello $msg";
    }
}

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testSay(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $chat = new DatatypeChat;
        $chat->say(array());
    }
}

输出失败:

 ✘ Say
   ├ Failed asserting that exception of type "TypeError" matches expected exception "InvalidArgumentException". Message was: "Argument 1 passed to DatatypeChat::say() must be of the type string, array given (..)

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

即使在方法 say 中有:

if (!is_string($msg)) {
   throw new \InvalidArgumentException('Message must be a string');
}

并且测试通过了一个数组而不是一个字符串:

$chat->say(array());

PHP没有到达代码:

throw new \InvalidArgumentException('Message must be a string');

因为键入 string 的类型提前引发了异常:

public function say(string $msg)

因此抛出 TypeError 而不是 InvalidArgumentException

再次出现“TypeError”类型的异常

知道我们不需要 if (!is_string($msg)) 来检查数据类型,因为 PHP 已经注意到如果我们在方法声明中指定数据类型 say(string $msg) 如果消息太长,我们可能想要抛出 InvalidArgumentException {4 }。

<?php
declare(strict_types=1);

class DatatypeChat
{
    public function say(string $msg)
    {
        if (strlen($msg) > 3) {
            throw new \InvalidArgumentException('Message is too long');
        }
        return "Hello $msg";
    }
}

class ExceptionTest extends \PHPUnit\Framework\TestCase
{
    public function testSayTooLong(): void
    {
        $this->expectException(\Exception::class);
        $chat = new DatatypeChat;
        $chat->say('I have more than 3 chars');
    }

    public function testSayDataType(): void
    {
        $this->expectException(\Exception::class);
        $chat = new DatatypeChat;
        $chat->say(array());
    }
}

还修改 ExceptionTest,因此我们有两种情况(测试方法)应该抛出 Exception - 第一个 testSayTooLong 当消息太长时,第二个 testSayDataType 当消息类型错误时。

在这两个测试中,我们期望通过使用而不是像 InvalidArgumentExceptionTypeError 这样的特定异常类只是一个通用 Exception

$this->expectException(\Exception::class);

测试结果是:

 ✔ Say too long
 ✘ Say data type
   ├ Failed asserting that exception of type "TypeError" matches expected exception "Exception". Message was: "Argument 1 passed to DatatypeChat::say() must be of the type string, array given (..)

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

testSayTooLong() 期待一个通用的 Exception 并使用

$this->expectException(\Exception::class);

抛出 InvalidArgumentException 时通过 OK

testSayDataType() 使用与描述相同的 $this->expectException(\Exception::class); Fails

断言“TypeError”类型的异常与预期的异常“Exception”匹配失败。

PHPUnit 抱怨 exception TypeError 不是 Exception 看起来令人困惑,否则它不会对 testSayDataType() 中的 $this->expectException(\Exception::class); 有任何问题,因为它没有任何问题testSayTooLong() 抛出 InvalidArgumentException 并期待:$this->expectException(\Exception::class);

问题是 PHPUnit 用上面的描述误导了你,因为 TypeError 不是 一个例外。 TypeError 不从 Exception 类或其任何其他子类扩展。

TypeError 实现 Throwable 接口参见 documentation

然而

InvalidArgumentException 扩展 LogicException documentation

LogicException 扩展 Exception documentation

因此 InvalidArgumentException 也扩展了 Exception

这就是为什么用 OK 和 $this->expectException(\Exception::class); 抛出 InvalidArgumentException 通过测试但抛出 TypeError 不会(它不会扩展 Exception

但是 ExceptionTypeError 都实现了 Throwable 接口。

因此在两个测试中都发生了变化

$this->expectException(\Exception::class);

$this->expectException(\Throwable::class);

使测试绿色:

 ✔ Say too long
 ✔ Say data type

OK (2 tests, 2 assertions)

See the list of Errors and Exception classes and how they are related to each other

需要明确一点:在单元测试中使用特定的异常或错误而不是通用的 ExceptionThrowable 是一种很好的做法,但是如果您现在遇到关于异常的误导性评论,您就会知道为什么 PHPUnit 的异常 {3 } 或其他异常错误实际上不是 Exception,而是 Throwable


这是我见过的最大的答案
不确定我是否应该标记为“将整个博文粘贴为答案”
F
Finesse

PHPUnit expectException 方法非常不方便,因为它只允许每个测试方法测试一个异常。

我已经制作了这个辅助函数来断言某些函数会引发异常:

/**
 * 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');
    });
}

绝对是所有答案中最好的解决方案!将其放入特征并打包!
多亏了数据提供者,您可以使用每个测试方法使用 $this->expectException() 测试多个异常。 More info
j
jchook

综合解决方案

PHPUnit 当前用于异常测试的“best practices”似乎……乏善可陈(docs)。

由于我 wanted more 比当前的 expectException 实现,我制作了一个 trait 用于我的测试用例。只有 ~50 lines of code

每个测试支持多个异常

支持抛出异常后调用的断言

强大而清晰的使用示例

标准断言语法

不仅支持消息、代码和类的断言

支持逆断言,assertNotThrows

支持 PHP 7 Throwable 错误

图书馆

我将 AssertThrows 特征发布到 Github 和 packagist,以便可以使用 composer 安装它。

简单示例

只是为了说明语法背后的精神:

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

?>

有点讽刺的是,你的单元测试包在 repo 中不包含单元测试。
@domdambrogia 感谢 @jean-beguin,它现在有单元测试。
这似乎是断言某些方法不会抛出的唯一解决方案,这通常确实非常必要。
A
Anuj Shrestha
public function testException() {
    try {
        $this->methodThatThrowsException();
        $this->fail("Expected Exception has not been raised.");
    } catch (Exception $ex) {
        $this->assertEquals("Exception message", $ex->getMessage());
    }
    
}

assertEquals() 的签名是 assertEquals(mixed $expected, mixed $actual...),与您的示例相反,所以它应该是 $this->assertEquals("Exception message", $ex->getMessage());
P
Potherca

这是您可以执行的所有异常断言。请注意,它们都是可选的。

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!');
    }
}

可以在 here 中找到文档。


这是不正确的,因为 PHP 在第一次抛出异常时停止。 PHPUnit 检查抛出的异常是否具有正确的类型并说“测试正常”,它甚至不知道第二个异常。
J
Jeff Puckett
/**
 * @expectedException Exception
 * @expectedExceptionMessage Amount has to be bigger then 0!
 */
public function testDepositNegative()
{
    $this->account->deposit(-7);
}

请注意 "/**",注意双“*”。只写“**”(asterix)会使你的代码失败。还要确保您使用的是最新版本的 phpUnit。在一些早期版本的 phpunit 中不支持 @expectedException 异常。我有 4.0 但它对我不起作用,我必须更新到 5.5 https://coderwall.com/p/mklvdw/install-phpunit-with-composer 才能使用作曲家进行更新。


a
aCiD

对于 PHPUnit 5.7.27 和 PHP 5.6 并在一个测试中测试多个异常,强制异常测试很重要。如果没有发生异常,单独使用异常处理来断言 Exception 的实例将跳过测试情况。

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());

}

s
sami klah
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 

   }

}

J
Jaume Mussons Abad

PhpUnit 是一个了不起的库,但是这一点有点令人沮丧。这就是为什么我们可以使用 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/');

如果我们在匿名函数中键入的代码没有抛出异常,就会抛出异常。

如果我们在匿名函数中键入的代码抛出异常,但其消息与预期的正则表达式不匹配,也会抛出异常。