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

Cha*_*rry 1 php spl phpunit

我有一个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');
    }

}
?>
Run Code Online (Sandbox Code Playgroud)

通常我会有一个生成目录名的方法,但是当我开始遇到问题时,我将其更改为绝对路径以排除原因.

这是实施:

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
Run Code Online (Sandbox Code Playgroud)

问题是测试通过,我可以通过测试生成的代码覆盖率来判断isWritable()返回true,并且SplFileObject::fwrite()在readonly对象上也返回非空值.

真正非常奇怪的部分是,在非单元测试示例中运行的相同代码失败,就像它应该的那样.

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

从这个运行index.php在Xdebug的结果显示未捕获的InvalidArgumentException来自FileLogger与预期的消息传递的文件是不可写.这完全令人费解,在两种情况下都运行相同的精确代码,但单元测试中的代码是"失败"而非单元测试代码正在按预期执行.


  1. 是的,该文件存在. SplFileObject如果不这样做会抛出异常.
  2. 在这两种情况下都运行完全相同的代码,正在运行的其他代码包括设置2个常量,文件目录和快捷方式DIRECTORY_SEPARATOR,以及设置类自动加载.但是,再次,这种情况在两种情况下都完全相同,并且在实际运行单元测试之前很久就会导致失败.
  3. 救命!

现在看看它的问题似乎相对简单.PHP在_www用户下运行,phpunit作为安装它的用户运行.这些用户具有不同的权限,这非常有意义.如果你遇到这个问题,我建议你看看edorian的答案并重新评估你是如何编写单元测试的.

edo*_*ian 5

首先:

对于单元测试有SplTempFileObject extends SplFileObject.

你通常不需要在磁盘上创建真正的文件,因为它总是很慢;)

对于isReadable/ isWriteablecheck in 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;
    }
}
Run Code Online (Sandbox Code Playgroud)