测试使用PHPUnit触发错误的方法的返回值

时间:2009-08-04 04:46:20

标签: php unit-testing exception phpunit

此问题特定于使用PHPUnit。

PHPUnit自动将php错误转换为异常。 有没有办法测试碰巧触发php错误的方法的返回值(内置错误或用户生成的错误通过 trigger_error )?

要测试的代码示例:

function load_file ($file)
{
    if (! file_exists($file)) {
        trigger_error("file {$file} does not exist", E_USER_WARNING);
        return false;
    }
    return file_get_contents($file);
}

这是我想写的测试类型:

public function testLoadFile ()
{
    $this->assertFalse(load_file('/some/non-existent/file'));
}

我遇到的问题是触发的错误导致我的单元测试失败(应该如此)。但是如果我试图捕获它,或者设置一个预期的异常,那么在触发错误之后的任何代码都不会执行,所以我无法测试该方法的返回值。

此示例不起作用:

public function testLoadFile ()
{
    $this->setExpectedException('Exception');
    $result = load_file('/some/non-existent/file');

    // code after this point never gets executed

    $this->assertFalse($result);
}

我是如何实现这一目标的?

5 个答案:

答案 0 :(得分:21)

在一个单元测试中无法做到这一点。如果你打破测试返回值,并将通知分成两个不同的测试,这是可能的。

PHPUnit的错误处理程序捕获PHP错误和通知并将它们转换为异常 - 根据定义,它会停止程序执行。您正在测试的功能永远不会返回。但是,您可以暂时禁用将错误转换为异常,即使在运行时也是如此。

使用示例可能更容易,因此,这两个测试应该是这样的:

public function testLoadFileTriggersErrorWhenFileNotFound()
{
    $this->setExpectedException('PHPUnit_Framework_Error_Warning'); // Or whichever exception it is
    $result = load_file('/some/non-existent/file');

}

public function testLoadFileRetunsFalseWhenFileNotFound()
{
    PHPUnit_Framework_Error_Warning::$enabled = FALSE;
    $result = load_file('/some/non-existent/file');

    $this->assertFalse($result);
}

这也有使您的测试更清晰,更清晰和自我记录的额外好处。

回复:评论: 这是一个很好的问题,直到我进行了几次测试之后我才知道。它看起来好像恢复默认/原始值,至少从PHPUnit 3.3.17(现在的当前稳定版本)开始。

所以,我实际上会修改上面看起来像这样:

public function testLoadFileRetunsFalseWhenFileNotFound()
{
    $warningEnabledOrig = PHPUnit_Framework_Error_Warning::$enabled;
    PHPUnit_Framework_Error_Warning::$enabled = false;

    $result = load_file('/some/non-existent/file');

    $this->assertFalse($result);

    PHPUnit_Framework_Error_Warning::$enabled = $warningEnabledOrig;
}

回复:第二条评论:

这不完全正确。我正在查看PHPUnit的错误处理程序,它的工作方式如下:

  • 如果是E_WARNING,请使用PHPUnit_Framework_Error_Warning作为例外类。
  • 如果是E_NOTICEE_STRICT错误,请使用PHPUnit_Framework_Error_Notice
  • 否则,请使用PHPUnit_Framework_Error作为例外类。

所以,是的,E_USER_*的错误没有变成PHPUnit的* _Warning或* _Notice类,它们仍然会转换为通用的PHPUnit_Framework_Error异常。

进一步思考

虽然它完全取决于函数的使用方式,但我可能会转而抛出一个实际的异常,而不是触发错误,如果是我的话。是的,这将改变方法的逻辑流程,以及使用该方法的代码......现在,当它无法读取文件时,执行不会停止。但是由您来决定所请求的文件是否存在是否真的是特殊行为。我倾向于使用异常方式而不是错误/警告/通知,因为它们更容易处理,测试并适用于您的应用程序流程。我通常会保留诸如折旧方法调用之类的通知等。

答案 1 :(得分:9)

使用phpunit.xml配置文件并禁用通知/警告/错误到异常转换。更多details in the manual。它基本上是这样的:

<phpunit convertErrorsToExceptions="false"
         convertNoticesToExceptions="false"
         convertWarningsToExceptions="false">
</phpunit>

答案 2 :(得分:3)

不是期望通用的“Exception”,而是期待“PHPUnit_Framework_Error”呢?

这样的事情可能会:

/**
 * @expectedException PHPUnit_Framework_Error
 */
public function testFailingInclude()
{
    include 'not_existing_file.php';
}

我认为,也可以写成:

public function testLoadFile ()
{
    $this->setExpectedException('PHPUnit_Framework_Error');
    $result = load_file('/some/non-existent/file');

    // code after this point never gets executed

    $this->assertFalse($result);
}

有关更多信息,请参阅Testing PHP Errors
特别是,它说(引用):

  

PHPUnit_Framework_Error_Notice和   PHPUnit_Framework_Error_Warning代表   PHP通知和警告。


查看我系统上的/usr/share/php/PHPUnit/TextUI/TestRunner.php文件,我看到(第198行及以下)

if (!$arguments['convertNoticesToExceptions']) {
    PHPUnit_Framework_Error_Notice::$enabled = FALSE;
}

if (!$arguments['convertWarningsToExceptions']) {
    PHPUnit_Framework_Error_Warning::$enabled = FALSE;
}

所以也许你必须通过某种参数来激活这种行为?但它似乎默认启用...

答案 3 :(得分:1)

实际上有一种方法可以测试返回值和抛出的异常(在这种情况下是由PHPUnit转换的错误)。

您只需执行以下操作:

public function testLoadFileTriggersErrorWhenFileNotFound()
{
    $this->assertFalse(@load_file('/some/non-existent/file'));

    $this->setExpectedException('PHPUnit_Framework_Error_Warning'); // Or whichever exception it is
    load_file('/some/non-existent/file');
}

请注意,要测试返回值,必须在函数调用上使用错误抑制运算符(函数名前的@)。这样就不会抛出任何异常,执行将继续。然后,您必须像往常一样设置预期的异常以测试错误。

您不能做的是在单元测试中测试多个异常。

答案 4 :(得分:0)

这个答案对聚会来说有点晚了,但是无论如何:

您可以使用Netsilik/BaseTestCase(MIT许可证)直接测试触发的通知/警告,而无需忽略它们或将其转换为异常。由于通知/警告不会转换为异常,因此不会暂停执行。

composer require netsilik/base-test-case


测试E_USER_NOTICE

<?php
namespace Tests;

class MyTestCase extends \Netsilik\Testing\BaseTestCase
{        
    public function test_whenNoticeTriggered_weCanTestForIt()
    {
        $foo = new Foo();
        $foo->bar();

        self::assertErrorTriggered(E_USER_NOTICE, 'The notice message');
    }
}

希望这对以后的人有帮助。