重新组织测试文件的选项,其中一个类依赖于另一个类?

时间:2011-09-05 02:25:34

标签: php phpunit

我正在重新组织遗留php项目的测试用例来测试套件并遇到测试依赖于另一个类的类的问题。以下是tests文件夹的原始目录布局。

tests/
   |  
   +- models/
   |     | ClassATest.php
   |     | ClassBTest.php
   |     | ...
   +- controllers/
   |     | ...

目前,没有测试套件。每项测试都是单独进行的。

要重新组织这些测试,首先,我创建 phpunit.xml 并将测试分组到如下的套件中。

phpunit.xml

<phpunit bootstrap="...">
    <testsuite name="Models">
        <directory>./models</directory>
    </testsuite>
    ...
</phpunit>

现在我遇到了麻烦。我们有两个模型,ClassA和ClassB,其中ClassB的方法依赖于ClassA。

class ClassA
{
    public function getValue(...)
    {
        ...
    }
}

class ClassB
{
    public function doSomething()
    {
        $a = new ClassA();
        $x = $a->getValue(...);
        // do something with $x
        ...
    }
 }

在这里,我认为在测试方面我们都看到了问题。要模拟ClassA :: getValue(), ClassBTest.php 已经(重新)定义了ClassA,如下所示。

ClassBTest.php

class ClassA
{
    public function getValue(...)
    {
        return 'a mock value';
    }
}

class ClassBTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $b = new ClassB();
        // Make an assertion with $b->doSomething()
    }
}

因此,当我们分别测试每个模型时,它会起作用。但是当在套件中运行时,它会导致错误,因为现在ClassA在运行进程的某个时刻被重新声明,并且phpunit被终止。

我的问题是如何处理这种情况。我尝试了一些选择。

  • 删除测试文件中的重新定义的类并使用依赖项注入。这种方式不是首选,因为我们不想修改依赖注入的模型类。
  • 使用组注释将此类测试文件排除为 ClassBTest.php 并单独运行排除的文件。这个选项不方便。

我们可以配置phpunit来运行进程中的一组文件和另一个进程中的另一组文件吗? PHP可以在运行时动态加载和卸载类吗?如果您有任何其他选择,请建议。

谢谢!

1 个答案:

答案 0 :(得分:2)

  

PHP可以在运行时动态加载和卸载一个类吗?如果您有任何其他选择,请建议。

是。这是测试无法重构的代码的最后手段。看看the runkit extension, method_rename。您可以重命名要“模拟”的orignal方法。创建模拟函数并在测试后重命名。

较新的php test helpers还提供了重命名功能。


或者您可以使用"runInIsolation" / "process-isolation"。每个测试用例都将在另一个PHP进程中运行。如果您只使用测试用例定义这些类,则不会遇到“已定义”的问题。


第三个选项不依赖于扩展或产生大量使您的测试套件变慢的php进程

看起来有点像hackisch但只是提供所有选项:

class ClassB
{
    public function doSomething()
    {
        $a = new ClassA();
        $x = $a->getValue(...);
        // do something with $x
        ...
    }
 }

没有重构您可以进行以下操作:

类ClassB {     公共功能doSomething()     {         $ a = $ this-&gt; getClassA();         $ x = $ a-&gt; getValue(...);         //用$ x做点什么         ...     }

protected function getClassA() {
    if(!$this->classA) { 
        return new ClassA(); 
    } 
    return $this->classA;
}

public function setClassAForTesting(ClassA $classA) {
    $this->classA = $classA;
}

}

会使用“代码进行测试”来混淆代码库,但使用setter injection可以使用依赖项注入进行测试,同时生产代码将像以前一样工作。

这种方法需要一些工作,但根据我的经验,它比使用PHP Extensions破解行为更具可持续性。