我目前正在使用PHPUnit尝试和我正在编写的内容一起开发测试,但是,我正在编写会话管理器,并且遇到了这样的问题......
会话处理类的构造函数是
private function __construct()
{
if (!headers_sent())
{
session_start();
self::$session_id = session_id();
}
}
但是,由于PHPUnit在开始测试之前发送文本,因此对此对象的任何测试都会返回失败的测试,因为HTTP“Headers”已经发送...
答案 0 :(得分:36)
嗯,您的会话管理器基本上已经被设计破坏了。为了能够测试某些东西,必须能够将其与副作用隔离开来。不幸的是,PHP以这样的方式设计,它鼓励自由使用全局状态(echo
,header
,exit
,session_start
等等。)
您可以做的最好的事情是隔离组件中可以在运行时交换的副作用。这样,您的测试可以使用模拟对象,而实时代码使用具有真正副作用的适配器。你会发现这对单身人士来说效果不佳,我猜你正在使用它。因此,您必须使用其他一些机制来获取分配给代码的共享对象。您可以从静态注册表开始,但如果您不介意进行一些学习,还有更好的解决方案。
如果你不能这样做,你总是可以选择编写集成测试。例如。使用PHPUnit相当于WebTestCase
。
答案 1 :(得分:20)
为phpunit创建一个引导程序文件,该文件调用:
session_start();
然后像这样启动phpunit:
phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/
引导程序文件在其他所有内容之前被调用,因此标题尚未发送,一切都应该正常工作。
答案 2 :(得分:19)
phpUnit在测试运行时输出输出,因此即使在第一次测试中,headers_sent()也会返回true。
要解决整个测试套件的这个问题,您只需在设置脚本中使用ob_start()。
例如,假设您有一个名为AllTests.php的文件,它是phpUnit首先加载的文件。该脚本可能如下所示:
<?php
ob_start();
require_once 'YourFramework/AllTests.php';
class AllTests {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite('YourFramework');
$suite->addTest(YourFramework_AllTests::suite());
return $suite;
}
}
答案 3 :(得分:12)
我有同样的问题,我通过调用带有--stderr标志的phpunit就解决了这个问题:
phpunit --stderr /path/to/your/test
希望它有所帮助!
答案 4 :(得分:5)
我认为“正确”的解决方案是创建一个非常简单的类(这么简单,不需要测试),这是PHP的会话相关函数的包装,并使用它而不是调用session_start()
等等。
在测试传递模拟对象中,而不是真正有状态的,不可测试的会话类。
private function __construct(SessionWrapper $wrapper)
{
if (!$wrapper->headers_sent())
{
$wrapper->session_start();
$this->session_id = $wrapper->session_id();
}
}
答案 5 :(得分:1)
I'm wondering why nobody have listed XDebug option:
/**
* @runInSeparateProcess
* @requires extension xdebug
*/
public function testGivenHeaderIsIncludedIntoResponse()
{
$customHeaderName = 'foo';
$customHeaderValue = 'bar';
// Here execute the code which is supposed to set headers
// ...
$expectedHeader = $customHeaderName . ': ' . $customHeaderValue;
$headers = xdebug_get_headers();
$this->assertContains($expectedHeader, $headers);
}
答案 6 :(得分:0)
在开始测试之前,你不能使用输出缓冲吗?如果缓冲输出的所有内容,则不应该设置任何标题,因为此时尚未向客户端发送任何输出。
即使在类中的某个地方使用OB,它也是可堆叠的,OB不应该影响内部发生的事情。
答案 7 :(得分:0)
据我所知,Zend Framework为Zend_Session包测试使用相同的输出缓冲。您可以查看他们的测试用例以帮助您入门。
答案 8 :(得分:0)
创建引导程序文件,指出4个帖子似乎是最简洁的方法。
通常使用PHP,我们必须维护,并尝试将某种工程规则添加到非常糟糕的遗留项目中。我们没有时间(或权威)抛弃整堆垃圾并重新开始,所以troelskn的第一个anwer并不总是可行的。 (如果我们可以回到最初的设计,那么我们可以完全放弃PHP并使用更现代的东西,比如ruby或python,而不是帮助延续Web开发世界的这个COBOL。)
如果您正在尝试为整个使用session_start或setcookie的模块编写单元测试,那么在boostrap文件中启动会话可以解决这些问题。
答案 9 :(得分:0)
因为我现在正在对我的引导程序进行单元测试(是的,我知道大多数人不这样做),我遇到了同样的问题(header()和session_start())。 我发现的解决方案相当简单,在您的unittest引导程序中定义一个常量,并在发送标题或启动会话之前简单地检查它:
// phpunit_bootstrap.php
define('UNITTEST_RUNNING', true);
// bootstrap.php (application bootstrap)
defined('UNITTEST_RUNNING') || define('UNITTEST_RUNNING', false);
.....
if(UNITTEST_RUNNING===false){
session_start();
}
我同意这在设计上并不完美,但我正在对现有应用进行单元测试,不希望重写大部件。我也使用相同的逻辑来使用__call()和__set()魔术方法测试私有方法。
public function __set($name, $value){
if(UNITTEST_RUNNING===true){
$name='_' . $name;
$this->$name=$value;
}
throw new Exception('__set() can only be used when unittesting!');
}
答案 10 :(得分:0)
您似乎需要注入会话才能测试代码。我使用的最佳选项是Aura.Auth,用于身份验证过程,并使用NullSession和NullSegment进行测试。
Aura testing with null sessions
Aura框架编写精美,您可以单独使用Aura.Auth,而不需要任何其他Aura框架依赖项。