13

这个问题特定于使用 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);
}

有什么想法我能做到这一点吗?

4

5 回答 5

22

There is no way to do this within one unit test. It is possible if you break up testing the return value, and the notice into two different tests.

PHPUnit's error handler catches PHP errors and notices and converts them into Exceptions--which by definition stops program execution. The function you are testing never returns at all. You can, however, temporarily disable the conversion of errors into exceptions, even at runtime.

This is probably easier with an example, so, here's what the two tests should look like:

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

This also has the added bonus of making your tests clearer, cleaner and self documenting.

Re: Comment: That's a great question, and I had no idea until I ran a couple of tests. It looks as if it will not restore the default/original value, at least as of PHPUnit 3.3.17 (the current stable release right now).

So, I would actually amend the above to look like so:

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

Re: Second Comment:

That's not completely true. I'm looking at PHPUnit's error handler, and it works as follows:

  • If it is an E_WARNING, use PHPUnit_Framework_Error_Warning as an exception class.
  • If it is an E_NOTICE or E_STRICT error, use PHPUnit_Framework_Error_Notice
  • Else, use PHPUnit_Framework_Error as the exception class.

So, yes, errors of the E_USER_* are not turned into PHPUnit's *_Warning or *_Notice class, they are still transformed into a generic PHPUnit_Framework_Error exception.

Further Thoughts

While it depends exactly on how the function is used, I'd probably switch to throwing an actual exception instead of triggering an error, if it were me. Yes, this would change the logic flow of the method, and the code that uses the method... right now the execution does not stop when it cannot read a file. But that's up to you to decide whether the requested file not existing is truly exceptional behaviour. I tend to use exceptions way more than errors/warnings/notices, because they are easier to handle, test and work into your application flow. I usually reserve the notices for things like depreciated method calls, etc.

于 2009-08-04T13:56:43.010 回答
9

使用phpunit.xml配置文件并禁用通知/警告/错误到异常的转换。手册中的更多细节。基本上是这样的:

<phpunit convertErrorsToExceptions="false"
         convertNoticesToExceptions="false"
         convertWarningsToExceptions="false">
</phpunit>
于 2009-08-04T09:40:51.957 回答
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_NoticePHPUnit_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;
}

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

于 2009-08-04T05:21:35.190 回答
2

实际上有一种方法可以测试返回值和抛出的异常(在这种情况下是由 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');
}

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

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

于 2013-12-03T23:28:25.403 回答
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');
    }
}

希望这对将来的某人有所帮助。

于 2019-08-14T09:24:48.620 回答