その特定のログのファイルとして使用するコンストラクターで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()
、読み取り専用オブジェクトでもnull以外の値を返すことです。
これの本当に、本当に奇妙な部分は、単体テスト以外の例で実行されたのとまったく同じコードが、本来あるべきように失敗することです。
$logFile = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
$SplFile = new \SplFileObject($logFile);
$Logger = new \libs\sprayfire\logger\FileLogger($SplFile);
これをfromから実行するとindex.php
、xdebugは、渡されたファイルが書き込み可能ではないという予期されたメッセージとともに、キャッチInvalidArgumentException
されていないことを示します。FileLogger
これは完全に不可解であり、両方の状況で同じ正確なコードが実行されていますが、単体テスト内のコードは「失敗」しており、単体テストされていないコードは期待どおりに実行されています。
- はい、ファイルは存在します。
SplFileObject
そうでない場合は例外をスローします。 - まったく同じコードが両方の状況で実行されています。実行されている他のコードには、2つの定数、ファイルディレクトリとへのショートカットの
DIRECTORY_SEPARATOR
設定、およびクラスの自動読み込みの設定が含まれます。ただし、これも両方の状況でまったく同じように発生し、この単体テストが実際に実行されるずっと前に失敗する可能性があります。 - ヘルプ!
今それを見ると、問題は比較的単純に見えます。PHPは_www
ユーザーの下で実行されており、phpunitはそれをインストールしたユーザーとして実行されています。これらのユーザーにはさまざまな権限があり、これは完全に理にかなっています。どういうわけかこの問題が発生している場合は、edorianの回答を確認し、単体テストの作成方法を再評価することをお勧めします。