为什么phpunit不';t在模拟类中运行_destruct(),以及如何强制它?

为什么phpunit不';t在模拟类中运行_destruct(),以及如何强制它?,php,unit-testing,mocking,phpunit,destructor,Php,Unit Testing,Mocking,Phpunit,Destructor,代码将解释一切: <?php class ATest extends PHPUnit_Framework_TestCase { public function testDestructorOnOriginalClass() { $a = new A(); // It unset($a);

代码将解释一切:

<?php

class ATest extends PHPUnit_Framework_TestCase
{
    public function testDestructorOnOriginalClass() {
        $a = new A();                                             // It
        unset($a);                                                // works
        echo " great!";                                           // great!
        $this->expectOutputString('It works great!');
    }

    public function testDestructorOnMockedClass() {
        $a = $this->getMock('A', array('someNonExistingMethod')); // It
        unset($a);                                                // works
        echo " great!";                                           // great!
        $this->expectOutputString('It works great!');
    }
}

class A {
    public function __construct()
    {
        echo "It";
    }

    public function __destruct()
    {
        echo " works";
    }
}
正如您在第二个测试中所看到的,它以错误的顺序打印
works
,可能是因为phpunit将对mock的引用存储在某处,并且在测试结束时调用了
\u destruct()
。。。好的,我已经检查了
getMock()
方法,它确实存储了对模拟对象的引用(
$this->mockObjects[]=$mockObject;
),有效地阻止了对象被破坏,因此永远不会调用
\u析构函数()

/**
 * Returns a mock object for the specified class.
 *
 * @param  string  $originalClassName
 * @param  array   $methods
 * @param  array   $arguments
 * @param  string  $mockClassName
 * @param  boolean $callOriginalConstructor
 * @param  boolean $callOriginalClone
 * @param  boolean $callAutoload
 * @param  boolean $cloneArguments
 * @return PHPUnit_Framework_MockObject_MockObject
 * @throws PHPUnit_Framework_Exception
 * @since  Method available since Release 3.0.0
 */
public function getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE, $cloneArguments = FALSE)
{
    $mockObject = PHPUnit_Framework_MockObject_Generator::getMock(
      $originalClassName,
      $methods,
      $arguments,
      $mockClassName,
      $callOriginalConstructor,
      $callOriginalClone,
      $callAutoload,
      $cloneArguments
    );

    $this->mockObjects[] = $mockObject;

    return $mockObject;
}

所以问题是,有没有办法防止这种情况?我认为在应该调用时忽略
\uu destruct()
是一个不好的限制。

您只是在取消设置局部变量,而不是销毁对象本身

该对象也由PHPUnit本身保存。因此仍然有一个引用,因此您的
unset()
不会导致
\uu destruct()


因此,当前行为无法更改。在phpunit问题跟踪器中打开一个bug。

问题是当您不将任何值传递给“
getMock
”方法的第二个参数时,phpunit将从您正在模拟的类中存根所有方法(包括“
\u destruct
”)

但若您至少指定了一个方法(甚至可能是不存在的方法),PHPUnit将只存根您在第二个参数中传递的这些方法

因此,如果您希望保留所有方法,但也希望创建mock,那么您应该这样做:

$mock = $this->getMock('A', array('someNonExistingMethod'));

如果更改此行,测试应该通过。

如果我们克隆模拟对象并取消设置它,则可以在测试期间调用克隆对象的
\u destruct()
。它可以帮助您测试
\uu destruct()


请注意,此方法无法阻止在测试结束时调用原始模拟对象的
\uu destruct()

Uhm,我在问题中指出,phpunit保留引用。我想问的是,这是否可以避免,这样mock的行为将与mock类完全一样。你的简单回答让我有点头疼:)因为这修复了我提供的测试用例,但我的真实代码提供了这个非空参数,代码仍然无法工作。我的测试用例的问题是操作发生的顺序——在最后每个对象都被销毁,这就是在您的建议之后这个测试用例成功的方式。而且,我不相信即使在
PHPUnit\u框架\u TestCase::mockObjects[]
中存在引用,也会调用
\u destruct()
。我更新了我的问题,你会明白我在说什么:)好的,我现在明白你的意思了。似乎PU在测试完成后才发布模拟引用。。我想不出任何方法来删除这个引用(我想首先-也许有可能直接删除它,但不幸的是$mockObjects不受保护,而是私有的),对不起。但也许这是一个线索,你应该小心在析构函数中保留任何逻辑。有时这并不取决于你,即使它会,我仍然认为这是phpunit中的一个bug,因为它在不应该的时候改变了模拟类的行为。目前,我正在尝试模拟PDO行为,当对象被破坏时,PDO将终止与数据库的连接。可能很难更改PDO类的逻辑:)可能我最终会使用自定义类扩展该类,并将其用作模拟,而不是由
getMock()
创建的模拟。然而,如果你能用phpunit的方式来做,那就更好了。这是一个理论问题还是你真的依赖于SUT中依赖类的析构函数行为?如果是这样,这可能是一个坏设计的迹象。这个问题不应该是一个真正的问题。目前,我尝试模拟PDO类,并模拟它在对象被破坏时结束数据库连接的行为(并且没有其他方法关闭连接)-所以这不是我可以更改的类(除了扩展它,如果用
getMock()
模拟不起作用,我可能会这样做).好吧,PDO是个特例。如果你想模拟它,你需要首先扩展它并覆盖构造函数,否则MockBuilder根本无法实例化它(假设你不想要实际的连接,否则你不会模拟它,是吗?)是的,没错,但这里面还有很多东西要解释。我只是好奇在模拟对象之后,我是否遗漏了
\uu destruct()
这种行为。谢谢你的帮助。
$mock = $this->getMock('A', array('someNonExistingMethod'));