TL;DL
タイトル通り、PHPUnit+log4phpは相性が悪く動かない可能性があります。
回避策として 直接 LoggerAppenderConsole
を修正するLoggerAppenderEcho
を利用するかprocessIsolation
を無効にします。
できればlog4phpを使わないようにしたほうがいいと思います。
発生条件
- PHPUnitを使用している
- オプションで
processIsolation
を有効にしている
- オプションで
- テスト対象、またはテストでlog4phpを使用している
- かつ、LoggerAppenderConsoleを使用している
何が起こるの?
サンプルプロジェクトをおいておきました。
こちらを実施すると画像のようなエラーになります。
なんで発生する?
log4phpの実装が悪いです。
PHPUnitのprocessIsolation
を有効にしていると、子プロセスのテスト結果がシリアライズされて、標準出力経由で親プロセスとやりとりされます(エラーでブワーッと出てしまっているのはこのシリアライズされた値です)。
通常、テスト中に出力された文字はバッファされて適切に扱われるのですが、log4phpが標準出力されるべき値を上書いてしまう場合があります。
class LoggerAppenderConsole extends LoggerAppender {
/** The standard otuput stream. */
const STDOUT = 'php://stdout'; // <- ここが原因!
どうすれば直る?
log4phpを使わない!(暴論)
すでにlog4phpプロジェクトは動いていなく、Issue,PRも何年も放置されています…(なので記事にしました)
また、PHP8.1以降ではDeprecatedとなりまともに動かせなくなります…
processIsolation
を無効にするのもOKだと思います。
とはいえ、大人の事情ですぐに対応できないという場合もあるかもしれません。
そのような場合はこうします。
class LoggerAppenderConsole extends LoggerAppender {
/** The standard otuput stream. */
//const STDOUT = 'php://stdout';
const STDOUT = 'php://output'; // <- 'php://output'にする!
(2023/03/17 追記)
この修正をすることでテストは動くようになりますが、LoggerAppenderConsoleを利用しているWEBアプリ側でログがechoされてしまうようになりました。
なので、LoggerAppenderConsoleが強制的に'php://stdout'を指定しているのは正しい動きになり、この修正は適当ではないです。
環境変数などを用いてテストのみLoggerAppenderConsoleではなく代わりにLoggerAppenderEchoを利用するような作りにするのが妥当だと思います。
'php://output'?
公式を見てみるとこんな使い分けになっています。
php://output
php://output は書き込み専用のストリームで、 print および echo と同じ方法での出力バッファへの書き込みを許可します。
print()
やecho()
は、特に何もしていなければ標準出力に、ob_start()
などでバッファリングを有効にしている場合はバッファに書き込むようになります。
対して、php://stdout
または定数STDOUT
を指定して出力した場合、 バッファリング中だったとしても、強制的に標準出力に文字を吐き出します
なので、processIsolation
有効時、本来はPHPUnitのシリアライズされた結果が欲しいのですが、log4phpによって余計な文字が付いてきたり上書きされてしまったりしてデシリアライズできなくなるみたいです。
おまけ
まだまだlog4php使われていそうですね…