Подтвердить что ты не робот

Проверить возвращаемое значение метода, который вызывает ошибку с помощью 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'));
}

Проблема, с которой я столкнулась, заключается в том, что вызванная ошибка приводит к сбою моего unit test (как следует). Но если я попытаюсь поймать его или установить ожидаемое исключение, то любой код, который после срабатывания ошибки будет выполнен, никогда не будет выполняться, поэтому у меня нет возможности проверить возвращаемое значение метода.

Этот пример не работает:

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

    // code after this point never gets executed

    $this->assertFalse($result);
}

Любые идеи, как я мог бы это достичь?

4b9b3361

Ответ 1

Невозможно сделать это за один unit test. Это возможно, если вы разбили тестирование возвращаемого значения и уведомление на два разных теста.

Обработчик ошибок 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);
}

У этого также есть дополнительный бонус, чтобы сделать ваши тесты более четкими, чистыми и самодокументированными.

Re: Комментарий: Это большой вопрос, и я понятия не имел, пока не проведу пару тестов. Похоже, что он не восстановит значение по умолчанию/оригиналу, по крайней мере, начиная с 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;
}

Re: Второй комментарий:

Это не совсем так. Я смотрю на обработчик ошибок PHPUnit, и он работает следующим образом:

  • Если это E_WARNING, используйте PHPUnit_Framework_Error_Warning как класс исключения.
  • Если это ошибка E_NOTICE или E_STRICT, используйте PHPUnit_Framework_Error_Notice
  • В противном случае используйте PHPUnit_Framework_Error как класс исключения.

Итак, да, ошибки E_USER_* не превращаются в класс PHPUnit * _Warning или * _Notice, они все равно преобразуются в общее исключение PHPUnit_Framework_Error.

Дальнейшие мысли

Хотя это зависит именно от того, как используется функция, я бы, вероятно, переключился на то, чтобы исключить фактическое исключение вместо запуска ошибки, если бы это был я. Да, это изменит логический поток метода и код, который использует этот метод... прямо сейчас выполнение не останавливается, когда он не может прочитать файл. Но вам решать, действительно ли запрашиваемый файл не является поистине исключительным поведением. Я предпочитаю использовать исключения больше, чем ошибки/предупреждения/уведомления, потому что их легче обрабатывать, тестировать и работать в потоке приложения. Я обычно резервирую уведомления для таких вещей, как вызовы с обесцененными вызовами и т.д.

Ответ 2

Используйте конфигурационный файл phpunit.xml и отключите уведомление/предупреждение/ошибку для исключения. Подробнее подробная информация в руководстве. Это в основном что-то вроде этого:

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

Ответ 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);
}

Для получения дополнительной информации см. Тестирование ошибок PHP
В частности, говорится (цитирует):

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

Значит, вам придется пройти какой-то параметр, чтобы активировать это поведение? Но он по умолчанию включен...

Ответ 4

На самом деле есть способ протестировать как возвращаемое значение, так и исключенное (в этом случае ошибку, преобразованную 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');
}

Обратите внимание, что для проверки возвращаемого значения вы должны использовать оператор подавления ошибок при вызове функции (@ перед именем функции). Таким образом, исключение не будет выбрано, и выполнение будет продолжено. Затем вы должны установить ожидаемое исключение, как обычно, для проверки ошибки.

То, что вы не можете сделать, это проверить несколько исключений в unit test.

Ответ 5

Этот ответ немного опоздал на вечеринку, но в любом случае:

Вы можете использовать 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');
    }
}

Надеюсь, это поможет кому-то в будущем.