有没有办法在PHPUnit中测试STDERR输出?

时间:2011-12-01 22:03:31

标签: php phpunit

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

班级PHPUnit_Extensions_OutputTestCase也没有用。

3 个答案:

答案 0 :(得分:8)

我没有看到使用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缓冲到内部缓冲区中,这是读者的练习。

答案 1 :(得分:6)

在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);构建它就会产生一个可测试的类,可以在应用程序中重复使用,而无需硬编码某种“这就是错误“进入课堂,这是自我,增加灵活性。”

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

答案 2 :(得分:2)

使用流过滤器捕获/重定向输出到STDERR。以下示例实际上是upercases并重定向到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();