47

我目前正在与 PHPUnit 一起尝试开发测试以及我正在编写的内容,但是,我目前正在编写会话管理器,并且在这样做时遇到了问题......

会话处理类的构造函数是

private function __construct()
{
    if (!headers_sent())
    {
        session_start();
        self::$session_id = session_id();
    }
}

但是,由于 PHPUnit 在开始测试之前会发送文本,因此对该对象的任何测试都会返回失败的测试,因为 HTTP“标头”已经发送...

4

11 回答 11

38

好吧,您的会话管理器基本上被设计破坏了。为了能够测试某些东西,必须能够将其与副作用隔离开来。不幸的是,PHP 的设计方式是鼓励自由使用全局状态(echoheaderexitsession_start)。

您可以做的最好的事情是隔离组件中的副作用,可以在运行时交换。这样,您的测试可以使用模拟对象,而实时代码使用具有真正副作用的适配器。您会发现这不适用于单例,我认为您正在使用单例。因此,您必须使用一些其他机制来将共享对象分发到您的代码中。您可以从静态注册表开始,但如果您不介意学习一点,还有更好的解决方案。

如果你不能这样做,你总是可以选择编写集成测试。例如。使用 PHPUnit 的WebTestCase.

于 2008-10-10T08:17:01.270 回答
20

为 phpunit 创建一个引导文件,该文件调用:

session_start();

然后像这样启动phpunit:

phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/

引导文件在其他所有内容之前被调用,因此标题尚未发送,一切都应该正常工作。

于 2009-11-09T13:39:01.667 回答
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;
    }
}
于 2010-10-30T15:44:01.037 回答
13

我遇到了同样的问题,我通过使用 --stderr 标志调用 phpunit 来解决它,就像这样:

phpunit --stderr /path/to/your/test

希望它可以帮助某人!

于 2014-07-16T10:33:58.930 回答
5

我认为“正确”的解决方案是创建一个非常简单的类(简单到不需要测试),它是 PHP 会话相关函数的包装器,并使用它而不是session_start()直接调用等。

在测试中通过模拟对象而不是真正的有状态、不可测试的会话类。

private function __construct(SessionWrapper $wrapper)
{
   if (!$wrapper->headers_sent())
   {
      $wrapper->session_start();
      $this->session_id = $wrapper->session_id();
   }
}
于 2008-11-12T00:01:40.543 回答
1

我想知道为什么没有人列出 XDebug 选项:

/**
 * @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);
}
于 2015-10-30T22:25:57.583 回答
0

你不能在开始测试之前使用输出缓冲吗?如果您缓冲所有输出的内容,则设置任何标头应该没有问题,因为此时尚未将任何输出发送到客户端。

即使 OB 在您的课程中的某处使用,它也是可堆叠的,并且 OB 不应该影响内部发生的事情。

于 2008-10-10T06:12:22.923 回答
0

据我所知,Zend Framework 对其 Zend_Session 包测试使用相同的输出缓冲。您可以查看他们的测试用例以帮助您入门。

于 2008-10-10T07:40:33.543 回答
0

引导文件的创建,指出 4 个帖子回来似乎是解决这个问题的最干净的方法。

通常使用 PHP,我们必须维护,并尝试将某种工程学科添加到非常简单地组合在一起的遗留项目中。我们没有时间(或授权)来丢弃整堆垃圾并重新开始,因此 troelskn 的第一个答案并不总是可能的前进方向。(如果我们可以回到最初的设计,那么我们可以完全抛弃 PHP 并使用更现代的东西,例如 ruby​​ 或 python,而不是帮助延续 Web 开发世界的这种 COBOL。)

如果您尝试为在整个模块中使用 session_start 或 setcookie 的模块编写单元测试,那么在 boostrap 文件中启动会话会解决这些问题。

于 2009-12-04T09:53:37.377 回答
0

因为我现在正在对我的引导程序进行单元测试(是的,我知道你们中的大多数人都不这样做),所以我遇到了同样的问题(header() 和 session_start())。我找到的解决方案相当简单,在您的单元测试引导程序中定义一个常量并在发送标头或启动会话之前简单地检查它:

// 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!');
 }
于 2011-10-19T13:39:17.077 回答
0

看来您需要注入会话以便测试代码。我使用的最佳选项是 Aura.Auth 用于身份验证过程并使用 NullSession 和 NullSegment 进行测试。

使用空会话进行 Aura 测试

Aura 框架编写精美,您可以单独使用 Aura.Auth,而无需任何其他 Aura 框架依赖项。

于 2016-01-08T10:51:16.483 回答