我有一个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,而readonly对象上的SplFileObject::fwrite()
也返回非空值。 / p>
真正非常奇怪的部分是在非单元测试示例中运行的相同代码失败,就像它应该的那样。
$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
,其中传递的文件的预期消息不可写。这完全令人费解,在两种情况下都运行相同的精确代码,但单元测试中的代码“失败”,非单元测试代码正在按预期执行。
SplFileObject
如果没有,则会抛出异常。DIRECTORY_SEPARATOR
的快捷方式,以及设置类自动加载。但是,同样,这种情况在两种情况下都会发生完全相同,并且在实际运行此单元测试之前很久就会导致失败。现在看一下这个问题似乎相对简单。 PHP在_www
用户下运行,phpunit作为安装它的用户运行。这些用户具有不同的权限,这非常有意义。如果你遇到这个问题我建议你看看edorian的答案并重新评估你是如何编写单元测试的。
答案 0 :(得分:5)
对于单元测试,有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;
}
}