【问题标题】:Mocking PHP functions in unit tests在单元测试中模拟 PHP 函数
【发布时间】:2010-11-17 17:07:28
【问题描述】:

我正在使用 SimpleTest 对一些 PHP 代码进行单元测试,但遇到了麻烦。在我对数据库类的测试中,我希望能够为 PHP mysql 函数设置一个期望值。在我对 mail 函数的包装类的测试中,我想模拟 PHP mail 函数。这些只是一些例子。

重点是:我不想(总是)测试我的 Mail 类是否发送电子邮件,我想测试它如何调用 mail 函数。我希望能够控制这些函数返回的内容。我希望能够测试我的数据库类,而不需要数据库、固定装置和其他所有东西。

我有一些测试 Ruby 代码的经验,Test::Unit 和 RSpec 使得单独测试代码变得非常容易。我是测试 PHP 的新手,感觉为了让我的测试通过,我测试的内容比我应该做的要多得多。

在 SimpleTest 或 PhpUnit 或其他一些测试框架中是否有一种方法可以使这成为可能或更容易?

【问题讨论】:

    标签: php unit-testing mocking simpletest


    【解决方案1】:

    不是以自动化方式。您可以做的是以某种方式编写代码,以便将外部依赖项包装在从外部传入的对象中。在您的生产环境中,您只需连接真正的适配器,但在测试期间,您可以将其连接到存根或模拟。

    如果你真的坚持,你可以使用runkit extension,它改变了php的编程模型,这样你就可以在运行时重新定义类和函数。然而,这是一个外部和非标准的扩展,所以请记住这一点。如上所述,事实上的标准是一种手动方法。

    【讨论】:

    • 我明白了。我有点期待(但不希望)这样的答案。我并不真正坚持,因为我想简化和加快测试,而不是让它变得更复杂。感谢您的回复!
    • 现在Mockery 似乎是way to go
    • @Franco 不适用于内置函数。
    【解决方案2】:

    Here is an interesting article 写了关于模拟全局 php 函数的文章。作者提出了一个非常有创意的解决方案,通过覆盖 SUT(被测服务)命名空间内的方法来“模拟”(关闭)全局 php 函数。

    这里的代码来自博客文章中模拟 time 函数的示例:

    <?php
    
    namespace My\Namespace;
    
    use PHPUnit_Framework_TestCase;
    
    /**
     * Override time() in current namespace for testing
     *
     * @return int
     */
    function time()
    {
        return SomeClassTest::$now ?: \time();
    }
    
    class SomeClassTest extends PHPUnit_Framework_TestCase
    {
        /**
         * @var int $now Timestamp that will be returned by time()
         */
        public static $now;
    
        /**
         * @var SomeClass $someClass Test subject
         */
        private $someClass;
    
        /**
         * Create test subject before test
         */
        protected function setUp()
        {
            parent::setUp();
            $this->someClass = new SomeClass;
        }
    
        /**
         * Reset custom time after test
         */
        protected function tearDown()
        {
            self::$now = null;
        }
    
        /*
         * Test cases
         */
        public function testOneHourAgoFromNoon()
        {
            self::$now = strtotime('12:00');
            $this->assertEquals('11:00', $this->someClass->oneHourAgo());
        }
        public function testOneHourAgoFromMidnight()
        {
            self::$now = strtotime('0:00');
            $this->assertEquals('23:00', $this->someClass->oneHourAgo());
        }
    }
    

    不确定这是否是一个好方法,但它确实运作良好,我认为这里值得一提。可能是一些讨论的食物......

    【讨论】:

      【解决方案3】:

      有一个 PHPUnit 扩展在内部使用 runkit,并且能够使用调用匹配器、参数约束和模拟对象可以做的所有事情。

      https://github.com/tcz/phpunit-mockfunction

      【讨论】:

        【解决方案4】:

        在 PHP 5.3+ 环境中,您可以通过破解命名空间来解决使用runkit 扩展的需要。函数调用的唯一要求不使用像\mysql_query() 这样的完全限定的命名空间——而且它们通常不会。然后,您可以通过在命名空间中定义测试来在测试中定义相同的函数,PHP 将调用您的函数而不是全局函数。我个人使用这种方法来存根time() 函数调用。这是nice example with the mockery framework

        【讨论】:

        • 你可以使用php-mock来创建这样的模拟。
        猜你喜欢
        • 2021-09-10
        • 1970-01-01
        • 2019-12-13
        • 2020-04-05
        • 2015-11-13
        • 2020-10-27
        • 2017-02-09
        • 2018-11-27
        • 1970-01-01
        相关资源
        最近更新 更多