有没有办法在PHPUnit中测试STDERR输出?
我有一个类输出到有没有办法在PHPUnit中测试STDERR输出?,php,phpunit,Php,Phpunit,我有一个类输出到STDERR,但是我很难找到让PHPUnit测试其输出的方法 类,PHPUnit\u Extensions\u OutputTestCase,也不起作用。我看不到一种方法可以像使用stdout那样缓冲stderr,因此我会重构您的类,将实际输出的调用移动到一个新方法。这将允许您在测试期间模拟该方法,以使用缓冲区验证输出或子类 例如,假设有一个类列出目录中的文件 class DirLister { public function list($path) {
STDERR
,但是我很难找到让PHPUnit测试其输出的方法
类,
PHPUnit\u Extensions\u OutputTestCase
,也不起作用。我看不到一种方法可以像使用stdout
那样缓冲stderr
,因此我会重构您的类,将实际输出的调用移动到一个新方法。这将允许您在测试期间模拟该方法,以使用缓冲区验证输出或子类
例如,假设有一个类列出目录中的文件
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 ;
}
}
第二,在测试中模拟或子类化它。如果您有一个简单的测试,或者不希望对输出调用太多,那么模拟就很容易了。如果要验证大量输出,则子类化以缓冲输出会更容易
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'
}
}
覆盖输出
以将所有$text
缓冲到内部缓冲区是留给读者的练习。您不能截取和写入(STDERR)代码>在phpunit的帮助下从测试用例中获取。因此,您甚至不能拦截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");
}
}
更新
在考虑过之后,我想说,没有像errorSteamWriter
这样的类也可以
只需拥有一个StreamWriter
,并使用新StreamWriter(STDERR)构建它
将产生一个很好的可测试类,该类可以在应用程序中为许多目的重用,而无需在类本身中硬编码某种类型的“这就是错误所在”,并增加灵活性
只是想添加这个选项以避免“丑陋”的测试选项:)使用流过滤器捕获/重定向输出到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();
该死。我们同时回答。您的更经典的方法(为什么我没有想到这一点?..streams玩起来很有趣)
)很好。也许可以去掉循环中的foreach(测试中的逻辑),但它是可靠的+1我确实喜欢使用流,您可以轻松地将内存缓冲和验证封装到测试助手类中。您可以添加setStream()
以允许注入,并将默认值添加到STDERR
以避免反射,但这显示了如果您对类的控制较少,您需要做什么。Nice+1一般来说,我不太喜欢setter注入(主要是在重构遗留类时使用),在这种特定情况下,我不会从功能上接触原始类。正如我们两个解决方案所做的那样。但是你提出了一个想法。。编辑:)下面是一个具体的例子。对于我们的日志类,我从来没有为它的单行方法编写过单元测试。它们在开发过程中显然会失败,永远不会再被触及。这是一项繁忙的工作,服务器没有任何好处,而且测试方法和正在测试的方法一样复杂。如果我想测试一个类是否正确地记录日志,我会模拟记录器本身,而不是它的输出流。一个好的命令行脚本不应该向stdout写入“错误”消息,但大多数PHP脚本无论如何都会这样做,可能是因为该语言不太清楚如何去做(也不像stdout那样为stderr提供输出缓冲);rmstrtoupper()
,并且可能不希望附加输出并使其回显两次。
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();