【问题标题】:PHPUnit: How to test a method which calls another function declared in different filePHPUnit:如何测试调用在不同文件中声明的另一个函数的方法
【发布时间】:2018-05-06 07:04:37
【问题描述】:

我正在尝试使用 PHPUnit 测试一个方法,它调用另一个函数(独立函数,没有类),该函数驻留在不同的文件中,该文件进行了一些非常好的计算并返回一个对象.

这是我的实际主要代码:

class CreateRecords
{
    public function createEntities($details)
    {
        if (trim($details['username']) == "") {
            $this->result = "Username is empty.";
        } else {
            $this->result = create_record($Details['username']);
        }       
        return $this->result;
    }
}

这个create_record 函数,(独立函数,没有类),它是核心函数,驻留在单独的文件中,它进行了很好的计算(调用了许多其他方法/函数) 并返回对象,无论成功与否。

我可以模拟createEntities 方法,但我想模拟create_record 函数,该函数执行所有计算并返回结果。

我见过很少有类似情况的帖子,

phpunit testing method that calls other class methods which need mock

PHPUnit mock method used in another class

但我无法理解,如何模拟在某些不同文件中声明的独立函数。

【问题讨论】:

  • 你试过使用这个函数'runkit_function_remove'吗?

标签: php phpunit


【解决方案1】:

在阅读了上述(相当不错的)答案并且您的 cmets 说您无法触摸测试类之后 - CreateRecords, 我可以建议另一种不理想但应该完成工作的解决方案:

创建一个继承\从CreateRecords - CreateRecordsExtended 扩展的新类。

扩展类应该只覆盖有问题的测试函数createEntities($details)。所以创建一个新函数并从原始函数中复制代码。

另外,创建一个新函数create_record()。 现在,在新的 createEntitied 中,调用您的 create_record 版本 - $this->create_record(),而不是调用全局函数。

现在你可以模拟它了!甚至因为这个类只用于测试,你甚至可以从它那里重新调整你想要的任何东西,甚至不必现在模拟它。

此类可以驻留在常规代码文件夹中,也可以作为测试类的邻居 - 因为它仅用于测试。

优点:

  • 根本没有修改现有代码。
  • 你仍然使用相同的确切 功能,除了你想要的模拟函数。

缺点:

  • 您将要测试的课程与您想要的课程不同,但是 它仍然足够接近。
  • 函数createEntities内的代码需要 被复制

也许它不是最理想的,但应该可以完成工作。希望我能帮上忙。

【讨论】:

    【解决方案2】:

    PHP5.3+ 中的命名空间为此提供了出色的解决方案,它允许您覆盖当前命名空间中的内置函数。

    从长远来看,将全局 create_record() 重构为一个可以注入到您的 CreateRecords 类中的类,然后进行模拟,将是一个不错的方法。在这种情况下,全局函数只是一个调用类的简单包装器,直到整个应用程序更新。

    不过,重新创建create_record($username) 并不难,而且对测试很有用。同样的技术也可以用于覆盖全局time() 函数。

    在测试文件中,添加一个新的(但本地的)命名空间:

    <?php
    namespace Test\DatabaseAccess;
    
    use DatabaseAccess\CreateRecord;
    use PHPUnit\Framework\TestCase;
    
    namespace DatabaseAccess {
        function create_record($username)
        {
            // pretend to do something
        }
    }
    
    class CreateRecordTest extends TestCase
    {
         // test to check CreateRecord class
         // which should call the `create_record`, above
    }
    

    这与 SymfonyBridge 系统用于创建 ClockMock 的技术相同 - 它动态地将 time()sleep() 等添加到您正在单元测试的命名空间(在本例中,命名空间 DatabaseAccess\CreateRecord ,而不是 Test\ 前缀命名空间)。 ClockMock(和 DnsMock)通过 eval() 调用来实现,但由于您明确知道命名空间,因此您可以将其写入测试文件本身以清楚起见。

    【讨论】:

    • 但是如果我无法更改此代码中的任何内容怎么办。由于有很多核心文件我根本无法更改,如果我决定了,仍然很费时间和精力。
    • 如果你有不可测试的代码,在你无法改变任何东西的情况下——那么你就完蛋了。但是 - 您显然可以更改和重构您的 CreateRecords 以使其更具可测试性。
    【解决方案3】:

    您可以创建将从外部函数返回结果的新方法。 然后你就可以模拟这个新方法了

    class CreateRecords
    {
        public function createEntities($details)
        {
            if (trim($details['username']) == "") {
                $this->result = "Username is empty.";
            } else {
                $this->result = $this->createRecord($Details['username']);
            }       
            return $this->result;
        }
    
        public function createRecord($username){
        return create_record($username);
        }
    }
    

    【讨论】:

    • 抱歉回复晚了。但是,如果我无法更改此代码中的任何内容怎么办。由于有很多核心文件我根本无法更改,如果我决定了,仍然很费时间和精力。
    猜你喜欢
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-12
    • 2021-07-25
    相关资源
    最近更新 更多