【问题标题】:Is there a way test STDERR output in PHPUnit?有没有办法在 PHPUnit 中测试 STDERR 输出?
【发布时间】:2012-06-02 19:40:38
【问题描述】:

我有一个输出到 STDERR 的类,但我找不到让 PHPUnit 测试其输出的方法。

PHPUnit_Extensions_OutputTestCase 的课程也不起作用。

【问题讨论】:

  • 好问题。一个好的命令行脚本不应该将“错误”消息写入标准输出,但大多数 PHP 脚本无论如何都会这样做,可能是因为该语言使得如何做到这一点变得不那么明显(并且也没有像标准输出那样为标准错误提供输出缓冲) )。

标签: php phpunit


【解决方案1】:

我没有看到缓冲stderr 的方法,因为你可以使用stdout,所以我会重构你的类以将执行实际输出的调用移动到一个新方法。这将允许您在测试期间模拟该方法,以验证输出或具有缓冲的子类。

例如,假设您有一个列出目录中文件的类。

class DirLister {
    public function list($path) {
        foreach (scandir($path) as $file) {
            echo $file . "\n";
        }
    }
}

首先,提取对echo 的调用。使其受到保护,以便您可以覆盖和/或模拟它。

class DirLister {
    public function list($path) {
        foreach (scandir($path) as $file) {
            $this->output($file . "\n");
        }
    }

    protected function output($text) {
        echo $text ;
    }
}

其次,在您的测试中模拟或子类化它。如果您有一个简单的测试或者不希望有很多电话到output,那么模拟很容易。如果要验证大量输出,则使用子类化来缓冲输出会更容易。

class DirListTest extends PHPUnit_Framework_TestCase {
    public function testHomeDir() {
        $list = $this->getMock('DirList', array('output'));
        $list->expects($this->at(0))->method('output')->with("a\n");
        $list->expects($this->at(1))->method('output')->with("b\n");
        $list->expects($this->at(2))->method('output')->with("c\n");
        $list->list('_files/DirList'); // contains files 'a', 'b', and 'c'
    }
}

重写output 以将所有$text 缓冲到内部缓冲区中作为练习留给读者。

【讨论】:

  • 该死的。我们同时回答。你更经典的方法(为什么我没有想到......流与;)一起玩很有趣)很好。也许摆脱循环中的 foreach (测试中的逻辑),但它是可靠的。 +1
【解决方案2】:

你不能在 phpunit 的帮助下从测试用例中截取和fwrite(STDERR);。就此而言,您甚至无法拦截 fwrite(STDOUT);,即使使用输出缓冲也不行。

因为我假设您并不想将STDERR 注入您的“errorOutputWriter”(因为在其他地方写该类没有任何意义)这是极少数情况之一我建议这种小技巧:

<?php 

class errorStreamWriterTest extends PHPUnit_Framework_TestCase {

    public function setUp() {
        $this->writer = new errorStreamWriter();
        $streamProp = new ReflectionProperty($this->writer, 'errorStream');
        $this->stream = fopen('php://memory', 'rw');
        $streamProp->setAccessible(true);
        $streamProp->setValue($this->writer, $this->stream);
    }

    public function testLog() {
        $this->writer->log("myMessage");
        fseek($this->stream, 0);
        $this->assertSame(
            "Error: myMessage",
            stream_get_contents($this->stream)
        );
    }

}

/* Original writer*/
class errorStreamWriter {
    public function log($message) {
        fwrite(STDERR, "Error: $message");
    }
}

// New writer:
class errorStreamWriter {

    protected $errorStream = STDERR;

    public function log($message) {
        fwrite($this->errorStream, "Error: $message");
    }

}

它取出 stderr 流并将其替换为内存中的流,然后在测试用例中读回该流以查看是否写入了正确的输出。

通常我肯定会说“在类中注入文件路径”,但 STDERR 对我来说没有任何意义,所以这将是我的解决方案。

phpunit stderrTest.php 
PHPUnit @package_version@ by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 5.00Mb

OK (1 test, 1 assertion)

更新

经过一番思考后,我会说没有像 errorSteamWriter 这样的思考作为一个班级也可能会奏效。

只要有一个StreamWriter 并用new StreamWriter(STDERR); 构造它,就会产生一个可很好测试的类,它可以在应用程序中用于很多目的,而无需硬编码某种“这是错误发生的地方”到类它是自我并增加灵活性。

只是想将此作为选项添加以避免“丑陋”的测试选项:)

【讨论】:

  • 我确实喜欢使用流,您可以轻松地将内存缓冲和验证封装到测试助手类中。您可以添加setStream() 以允许注入并默认为STDERR 以避免反射,但这表明如果您对类的控制较少,您需要做什么。不错 +1
  • 我不是一般的 setter 注入的忠实拥护者(主要是我将它用于重构遗留),在这种特定情况下,我不会在功能上触及原始类。正如我们的两个解决方案所做的那样。但是你提出了一个想法.. 编辑 :)
  • 这里有一个具体的例子会有所帮助。对于我们的日志记录类,我从不费心为其单行方法编写单元测试。它们显然会在开发过程中失败,并且再也不会被触及。工作繁忙,服务器没有任何好处,测试方法和被测试的方法一样复杂。如果我想测试一个类是否正确记录,我会模拟 Logger 本身而不是它的输出流。
【解决方案3】:

使用流过滤器将输出捕获/重定向到 STDERR。以下示例实际上是大写并重定向到 STDOUT,但传达了基本思想。

class RedirectFilter extends php_user_filter {
  static $redirect;

  function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
  $bucket->data = strtoupper($bucket->data);
  $consumed += $bucket->datalen;
  stream_bucket_append($out, $bucket);
  fwrite(STDOUT, $bucket->data);
}
return PSFS_PASS_ON;
  }
}

stream_filter_register("redirect", "RedirectFilter")
or die("Failed to register filter");

function start_redirect() {
  RedirectFilter::$redirect = stream_filter_prepend(STDERR, "redirect", STREAM_FILTER_WRITE);
}

function stop_redirect() {
  stream_filter_remove( RedirectFilter::$redirect);
}

start_redirect();
fwrite(STDERR, "test 1\n");
stop_redirect();

【讨论】:

  • 很好,但有一些小问题; rm strtoupper(),并且可能不想附加输出并让它回显两次。
猜你喜欢
  • 2015-06-18
  • 2018-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-15
  • 2020-12-21
  • 2012-09-18
  • 1970-01-01
相关资源
最近更新 更多