使用 PHPUnit 模拟 PDO 对象

Posted

技术标签:

【中文标题】使用 PHPUnit 模拟 PDO 对象【英文标题】:Mocking The PDO Object using PHPUnit 【发布时间】:2011-03-09 12:17:12 【问题描述】:

我在用 phpUnit 模拟 PDO 对象时遇到了困难。

网络上似乎没有太多关于我的问题的信息,但我可以收集到的信息:

    PDO 具有“最终”__wakeup 和 __sleep 防止它被序列化的方法。 PHPunit 的模拟对象实现会在某些时候序列化对象。 发生这种情况时,单元测试会因 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】:

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

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

和以往一样,与 eval 一样,如果 runkit 似乎是答案,那么问题可能是错误的 :)

【讨论】:

我不认为将runkiteval 用于测试目的有什么问题。【参考方案2】:

$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'));

像这样创建模拟执行原始构造函数,但是由于有了 mockPDO 测试助手,它现在是无害的,您可以继续测试。

【讨论】:

你是爸爸!非常感谢这工作正常。我已经放弃解决这个问题了! 我遇到了与原始发帖人相同的问题,并使用了您的解决方案。但是,现在我的打字提示不再将其视为 PDO。 must be an instance of PDO, instance of Mock_PDOMock_96936f72 given @nvanesch 它似乎也不算是instanceof \PDO。我最终做的只是直接实例化 mockPDO 类。【参考方案3】:

你是在你的测试用例中实例化你的测试用例吗?

$classToTest = new MyTest($pdoMock);

现在,您实际上是在测试您的测试用例。它应该更像:

$classToTest = new My($pdoMock);

【讨论】:

这绝对是原始问题中的一个错误。

以上是关于使用 PHPUnit 模拟 PDO 对象的主要内容,如果未能解决你的问题,请参考以下文章

FilterIterator 应该是 PHPUnit\Framework\TestSuiteIterator 的实例

PHPUnit 模拟对象和方法类型提示

Composer 更新 codeception - phpunit 不更新

PHPUnit 问题断言模拟是使用 Laravel 的 ReflectionProperty 对象

PHPUnit测试使用pdo的受保护静态方法

是否有可能使用 PHPUnit 模拟对象来调用一个神奇的 __call() 方法?