代码之家  ›  专栏  ›  技术社区  ›  Mez

使用需要发送标头的项目进行单元测试

  •  47
  • Mez  · 技术社区  · 17 年前

    我目前正在与PHPUnit合作,尝试在编写测试的同时开发测试,但是,我目前正在编写会话管理器,并且在编写过程中遇到了问题。。。

    Session处理类的构造函数是

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

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

    11 回复  |  直到 7 年前
        1
  •  38
  •   duckbrain troelskn    10 年前

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

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

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

        2
  •  20
  •   Dominik    13 年前

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

    session_start();
    

    然后像这样启动phpunit:

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

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

        3
  •  19
  •   Michael    15 年前

    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;
        }
    }
    
        4
  •  13
  •   Juanjo Lainez Reche    11 年前

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

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

    希望它能帮助别人!

        5
  •  5
  •   Kornel    17 年前

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

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

    private function __construct(SessionWrapper $wrapper)
    {
       if (!$wrapper->headers_sent())
       {
          $wrapper->session_start();
          $this->session_id = $wrapper->session_id();
       }
    }
    
        6
  •  1
  •   Tim Bezhashvyly    10 年前

    我想知道为什么没有人列出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);
    }
    
        7
  •  0
  •   pilsetnieks    17 年前

    在开始测试之前,你不能使用输出缓冲吗?若你们缓冲了所有输出,你们在设置任何头文件时应该不会有问题,因为那时还没有输出发送到客户端。

    即使OB在你的类中的某个地方使用,它也是可堆叠的,OB不应该影响里面发生的事情。

        8
  •  0
  •       17 年前

    据我所知,Zend Framework在Zend_Session包测试中使用了相同的输出缓冲区。你可以看看他们的测试用例来开始。

        9
  •  0
  •   user224633 user224633    15 年前

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

    对于PHP,我们经常需要维护,并尝试为那些拼凑得很糟糕的遗留项目添加某种工程纪律。我们没有时间(或权力)扔掉整堆垃圾,重新开始,所以特罗尔斯克的第一个答案并不总是可能的。 (如果我们能回到最初的设计,那么我们就可以完全抛弃PHP,使用更现代的东西,比如ruby或python,而不是帮助延续web开发世界的COBOL。)

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

        10
  •  0
  •   Arron    14 年前

    当我现在正在对我的引导程序进行单元测试时(是的,我知道你们大多数人都没有这样做),我遇到了同样的问题(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!');
     }
    
        11
  •  0
  •   lost in binary    9 年前

    您似乎需要注入会话,以便测试代码。我用过的最好的选择是Aura。对身份验证过程进行身份验证,并使用NullSession和NullSegment进行测试。

    Aura testing with null sessions

    Aura框架写得很漂亮,你可以使用Aura。独立认证,无需任何其他Aura框架依赖。

    推荐文章