使用 PHPUnit 模拟 PDO 对象

2022-08-30 13:59:41

我很难用PHPUnit模拟PDO对象。

网络上似乎没有太多关于我的问题的信息,但从我能收集到的信息来看:

  1. PDO具有“最终”__wakeup和__sleep方法,可防止其被序列化。
  2. PHPunit 的模拟对象实现会在某个时候序列化该对象。
  3. 然后,单元测试将失败,并在发生这种情况时由 PDO 生成 PHP 错误。

有一个功能旨在通过向单元测试中添加以下行来防止此行为:

class MyTest extends PHPUnit_Framework_TestCase

{    
    protected $backupGlobals = FALSE;
     // ...

}

资料来源:http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html

这对我不起作用,我的测试仍然产生错误。

完整的测试代码:

class MyTest extends PHPUnit_Framework_TestCase
{

    /**
     * @var MyTest
     */
    private $MyTestr;

    protected $backupGlobals = FALSE;

    /**
     * Prepares the environment before running a test.
     */
    protected function setUp()
    {
        parent::setUp();

    }

    /**
     * Cleans up the environment after running a test.
     */
    protected function tearDown()
    {

        parent::tearDown();
    }

    public function __construct()
    {

        $this->backupGlobals = false;
        parent::__construct();

    }


    /**
     * Tests MyTest->__construct()
     */
    public function test__construct()
    {

        $pdoMock = $this->getMock('PDO', array('prepare'), array(), '', false);

        $classToTest = new MyTest($pdoMock);

        // Assert stuff here!


    }

    // More test code.......

任何PHPUnit专业人士都帮我一把吗?

谢谢


答案 1

$backupGlobals对您没有帮助,因为此错误来自其他地方。PHPUnit 3.5.2(也可能是早期版本)在PHPUnit/Framework/MockObject/Generator中具有以下代码.php

    if ($callOriginalConstructor &&
        !interface_exists($originalClassName, $callAutoload)) {
        if (count($arguments) == 0) {
            $mockObject = new $mock['mockClassName'];
        } else {
            $mockClass  = new ReflectionClass($mock['mockClassName']);
            $mockObject = $mockClass->newInstanceArgs($arguments);
        }
    } else {
        // Use a trick to create a new object of a class
        // without invoking its constructor.
        $mockObject = unserialize(
          sprintf(
            'O:%d:"%s":0:{}',
            strlen($mock['mockClassName']), $mock['mockClassName']
          )
        );
    }

当您要求getMock不执行原始构造函数并且它将立即失败PDO时,将使用反序列化的“技巧”。

那么,如何解决它呢?

一种选择是创建一个像这样的测试帮助程序

class mockPDO extends PDO
{
    public function __construct ()
    {}

}

这里的目标是摆脱您不需要的原始PDO构造函数。然后,将测试代码更改为:

$pdoMock = $this->getMock('mockPDO', array('prepare'));

像这样创建模拟执行原始构造函数,但由于它现在由于模拟PDO测试帮助程序而无害,因此您可以继续测试。


答案 2

我能想到的最好的方法是使用runkit,并使用runkit_function_redefine将两个最终方法重新定义为受保护的方法。

不要 for get 在 php 中启用runkit.internal_override设置.ini。

和以往一样,就像eval一样,如果runkit看起来像是答案,那么这个问题可能是错误的:)


推荐