使用 PHPUnit 测试 error_log

testing error_log with PHPUnit

我有这个功能,我想测试一下:

class Logger {
  function error($msg){
    if (is_string($msg)){
      error_log($msg);
      die($msg);
    } elseif (is_object($msg)){
      error_log($msg.' '.$msg->getTraceAsString());
      die('exception');
    } else {
      var_dump($msg);
      die('error');
    }
  }

我想在不记录 $msg 的情况下测试此功能。有没有办法在不记录的情况下确定 error_log 是否有效?我尝试使用 setExpectedException 但我无法捕获错误并且它一直在记录。

显而易见的答案是一个简单的 alias/proxy-function,它本身在 Logger class 中调用 error_log(可以很容易地模拟,并检查它的设置),

然而,要实际测试本机 error_log 函数(在原始 class 中没有代理),可以使用名称空间来完成。测试最终会定义为与原始代码相同的命名空间,然后在测试 class 之后添加一个函数 - 在本例中为 error_log() - 但该函数也在命名空间中定义 -因此 运行 优先于本机函数的根命名空间等价物。

不幸的是,你不能用die(或其别名exit)做同样的覆盖。它们是 'language constructs',不能像 error_log 那样被覆盖。

<?php
namespace abc;
use abc\Logger;

class ThreeTest extends \PHPUnit_Framework_TestCase
{
    public function setUp() { $this->l = new Logger(); }
    // test code to exercise 'abc\Logger'

}

// Now define a function, still inside the namespace '\abc'.
public function error_log($msg)
{
   // this will be called from abc\Logger::error
   // instead of the native error_log() function
   echo "ERR: $msg, ";
}

您可以使用像 php-mock 这样的函数模拟框架(还有其他框架)来模拟对 error_log 的调用(并检查它是否使用您期望的参数调用)。

不幸的是,您将无法将其用于 die-construct,因为它不是普通函数,而是一种语言结构。

我会将 die() 替换为 'throw new \Exception()'(或任何其他适当的异常),然后

  • 测试抛出的异常和
  • 可以在您的编程中决定是在调用记录器时停止执行,还是通过将调用包装到 try/catch
  • 中来继续执行

但我也会问自己在调用记录器时是否必须停止执行

在变量中捕获 error_log() 输出

如果您想以一种允许您使用 PHPUnit 断言检查它的方式重定向 error_log() 输出,以下代码适用于我:

$errorLogTmpfile = tmpfile();
$errorLogLocationBackup = ini_set('error_log', stream_get_meta_data($errorLogTmpfile)['uri']);
error_log("Test for this message");
ini_set('error_log', $errorLogLocationBackup);
$result = stream_get_contents($errorLogTmpfile);
// Result: [11-May-2022 22:27:08 UTC] Test for this message

如您所见,它使用一个临时文件来收集输出,然后将内容抓取到一个变量中并重置 error_log 配置。

Re-usable 方法

就个人而言,我将其组织成一对方法,我将这些方法注入到具有特征的 PHPUnit 对象中,这样我就可以 re-use 它们。

当然下面的代码不能开箱即用,但它可以演示如何制作这个系统re-usable:

trait WithWPTestCaseGeneralTools {
    
    var $gvErrorLogLocationBackup = "";
    var $gvErrorLogTmpfile = "";

    public function gvErrorLogStartListening() {
        
        $this->gvErrorLogTmpfile = tmpfile();
        $streamUri = stream_get_meta_data($this->gvErrorLogTmpfile)['uri'];
        $this->gvErrorLogLocationBackup = ini_set('error_log', $streamUri);
    }

    public function gvErrorLogGetContents() {
        
        ini_set('error_log', $this->gvErrorLogLocationBackup);      
        return stream_get_contents($this->gvErrorLogTmpfile);
    }
}

您当然可以使用几个使用全局变量的函数来实现相同的目的,如果您需要的话,我会把它留给您!