PHPUnit和SplFileObject在只读对象上返回true isWritable


PHPUnit and SplFileObject returning true isWritable on read-only object

我有一个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中显示FileLogger中未捕获的InvalidArgumentException,并显示所传递的文件不可写的预期消息。这完全令人困惑,在两种情况下都运行相同的代码,但是单元测试中的代码"失败",而非单元测试的代码则按预期执行。


  1. 是,文件存在。如果没有,SplFileObject将抛出异常。
  2. 在这两种情况下都运行了完全相同的代码,正在运行的其他代码包括设置2个常量,一个文件目录和DIRECTORY_SEPARATOR的快捷方式,并设置类自动加载。但是,在这两种情况下发生的情况完全相同,并且在实际运行单元测试之前很久就会导致失败。
  3. 帮助!

现在看来,问题似乎相对简单。PHP以_www用户运行,phpunit以安装它的用户运行。这些用户有不同的权限,这是完全合理的。如果你遇到了这个问题,我建议你看看edorian的答案,并重新评估你是如何编写单元测试的。

首先:

对于单元测试,有SplTempFileObject extends SplFileObject .

你通常不需要在磁盘上创建真正的文件,因为它无论如何都很慢;)

对于phpunit测试中的isReadable/isWriteable检查,您通常在磁盘上创建不可读/可写的文件或在适用的情况下使用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;
    }
}