PHPUnit и SplFileObject возвращают true isWritable для объекта только для чтения - PullRequest
1 голос
/ 26 декабря 2011

У меня есть Logger интерфейс, который принимает SplFileObject в конструкторе для использования в качестве файла для этого конкретного журнала.Существует также метод log($timestamp, $message), который позволяет вести запись в журнал.В моей первой реализации при создании нового объекта и передаче только для чтения SplFileObject должно быть выдано исключение.Я написал соответствующий модульный тест:

<?php
class FileLoggerTest extends PHPUnit_Framework_TestCase {

    /**
     * @expectedException \InvalidArgumentException
     */
    public function testReadOnlyFileObjectFailure() {
        $file = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
        $LogFile = new \SplFileObject($file);
        $Logger = new \libs\sprayfire\logger\FileLogger($LogFile);
        $Logger->log('test', 'something');
    }

}
?>

Обычно у меня был бы метод создания имени каталога, но когда я начинал сталкиваться с проблемами, я менял его на абсолютный путь, чтобы исключить его в качестве причины..

А вот и реализация:

namespace libs\sprayfire\logger;
use \SplFileObject as SplFileObject;
use \InvalidArgumentException as InvalidArgumentException;
use libs\sprayfire\logger\Logger as Logger;

    /**
     * @brief A framework implemented class that adds a timestamp log message to
     * the end of an injected file.
     */
    class FileLogger implements Logger  {

        /**
         * @brief A SplFileObject that should be used to write log messages to.
         *
         * @property $LogFile
         */
        protected $LogFile;

        /**
         * @param $LogFile SplFileObject that should have log messages written to
         */
        public function __construct(SplFileObject $LogFile) {
            $this->LogFile = $LogFile;
            $this->throwExceptionIfFileNotWritable();
        }

        /**
         * @throws InvalidArgumentException
         */
        protected function throwExceptionIfFileNotWritable() {
            $isWritable = $this->LogFile->isWritable();
            if (!$isWritable) {
                throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
            }
        }

        /**
         * @param $timestamp A formatted timestamp string
         * @param $message The message string to log
         * @return boolean true if the message was logged, false if it wasn't
         */
        public function log($timestamp, $message) {
            if (!isset($timestamp) || empty($timestamp)) {
                $timestamp = 'No timestamp given';
            }

            if (!isset($message) || empty($message)) {
                $message = 'Attempting to log an empty message';
            }

            $separator = ' := ';
            $message = $timestamp . $separator . $message;
            $wasWritten = $this->LogFile->fwrite($message);
            if (!isset($wasWritten)) {
                return false;
            }
            return true;
        }

    }

    // End FileLogger

Проблема в том, что тест пройден, и я могу определить с помощью покрытия кода, сгенерированного тестом, что isWritable() возвращает true иSplFileObject::fwrite() для объекта, доступного только для чтения, также возвращает ненулевое значение.

Действительно, действительно странная часть этого состоит в том, что тот же самый код, запущенный в примере не модульного теста, терпит неудачу, как и должен.

$logFile = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
$SplFile = new \SplFileObject($logFile);
$Logger = new \libs\sprayfire\logger\FileLogger($SplFile);

Запуск этого из index.php приводит к тому, что xdebug показывает неперехваченное InvalidArgumentException из FileLogger с ожидаемым сообщением о том, что переданный файл недоступен для записи.Это полностью сбивает с толку, один и тот же точный код запускается в обеих ситуациях, но код внутри модульного теста «не работает», и код, не проверенный модулем, работает как положено.


  1. Да, файл существует.SplFileObject выдаст исключение, если этого не произойдет.
  2. В обеих ситуациях выполняется один и тот же код, другой запускаемый код включает в себя настройку 2 констант, каталог файлов и ярлык дляDIRECTORY_SEPARATOR и настройка автозагрузки классов.Но, опять же, это происходит одинаково в обеих ситуациях и может привести к сбою задолго до того, как этот модульный тест действительно будет запущен.
  3. Help!

Просмотрсейчас проблема кажется относительно простой.PHP работает под пользователем _www, а phpunit работает как пользователь, установивший его.Эти пользователи имеют разные разрешения, что имеет смысл.Если вы как-то столкнулись с этой проблемой, я предлагаю вам взглянуть на ответ edorian и переоценить, как вы пишете свои модульные тесты.

1 Ответ

5 голосов
/ 27 декабря 2011

Первый раз:

Для модульного тестирования есть SplTempFileObject extends SplFileObject.

Обычно для этого вам не нужно создавать реальные файлы на диске, так как в любом случае он медленный;)

Для проверки isReadable / isWriteable в тестах phpunit вы обычно создаете эфир, создаете не читаемые / записываемые файлы на диске или используете vfsStreamWrapper , где это применимо.Это также работает с SplFileObject.

Наша проблема:

В тесте последняя строка должна быть удалена.Вы исключаете исключение во время конструирования, так что давайте покончим с этим;)

Что я нашел странным, так это то, что у вас есть абсолютный путь там.Структура вашей корневой папки действительно начинается с '/Library/WebServer/Documents/?В основном я запутался, потому что это означало бы, что ваши тесты находятся в каталоге "WebServer".В любом случае ... продолжаем:

"Работает для меня"

Прилагается отдельная версия теста, которая работает и выдает исключение, как и ожидалось.

Помимо сообщения о том, что проблема, кажется, где-то еще в вашей настройке, я не вижу много, что я могу сделать здесь.Возможно, попробуйте InvalidArgumentException без / или попробуйте выполнить тестирование изолированно / с только что созданным файлом.

PHPUnit не мешает работе с файлами, так что я не согласен с этим,Работает ли приведенный ниже пример кода для вас?:)

phpunit mep.php 
PHPUnit 3.6.5 by Sebastian Bergmann.

.

Time: 1 second, Memory: 5.00Mb

OK (1 test, 1 assertion)

<?php

class FileLoggerTest extends PHPUnit_Framework_TestCase {

    /**
     * @expectedException InvalidArgumentException
     */
    public function testReadOnlyFileObjectFailure() {
        $file = __DIR__."/_files/test-log.txt";
        touch($file);
        chmod($file, 0444);
        $LogFile = new \SplFileObject($file);
        $Logger = new FileLogger($LogFile);
    }

}


class FileLogger {

    protected $LogFile;
    public function __construct(SplFileObject $LogFile) {
        $this->LogFile = $LogFile;
        $this->throwExceptionIfFileNotWritable();
    }

    /**
     * @throws InvalidArgumentException
     */
    protected function throwExceptionIfFileNotWritable() {
        $isWritable = $this->LogFile->isWritable();
        if (!$isWritable) {
            throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
        }
    }

    /**
     * @param $timestamp A formatted timestamp string
     * @param $message The message string to log
     * @return boolean true if the message was logged, false if it wasn't
     */
    public function log($timestamp, $message) {
        if (!isset($timestamp) || empty($timestamp)) {
            $timestamp = 'No timestamp given';
        }

        if (!isset($message) || empty($message)) {
            $message = 'Attempting to log an empty message';
        }

        $separator = ' := ';
        $message = $timestamp . $separator . $message;
        $wasWritten = $this->LogFile->fwrite($message);
        if (!isset($wasWritten)) {
            return false;
        }
        return true;
    }
}
...