【问题标题】:'Cannot redeclare class' error when trying to mock out dependencies in PHPUnit尝试在 PHPUnit 中模拟依赖项时出现“无法重新声明类”错误
【发布时间】:2012-04-08 12:40:00
【问题描述】:

我正在现有代码库上使用 PHPUnit 实施单元测试。我对单元测试相当陌生,但我知道目标是完全隔离正在测试的代码。这对我的代码库来说很困难,因为许多类都依赖于代码库中的其他类。

依赖项被硬编码到类中,因此无法使用依赖项注入。我也不想仅仅为了测试而重构现有代码。 因此,为了将每个类与其依赖项隔离开来,我创建了一个“模拟”类库(不是通过使用 PHPUnit 的模拟框架,而是通过创建一个包含存根函数的类库,这些存根函数根据特定的返回值输入)。

问题是,如果在运行 phpunit 期间,我有一个调用模拟类的测试,然后我尝试测试实际类,我会收到一个致命错误,因为 PHP 认为这是重新声明类。这是我的意思的简化示例。请注意,即使我取消设置已包含的类的所有实例并在 tearDown 方法中清除包含路径,这仍然会失败。同样,我是单元测试的新手,所以如果我以错误的方式处理这个问题或遗漏了一些明显的东西,请告诉我。

一个更大的问题可能是这种方法是否会朝着隔离代码的方向发展,以及使用真实对象作为我的类的依赖项是否真的有好处。

#### real A

require_once 'b.class.php';

class A {   
    private $b;

    public function __construct() {
        $this->b = new B();
    }

    public function myMethod($foo) {
        return $this->b->exampleMethod($foo);
    }
}


#### real B

class B {
    public function exampleMethod($foo) {
        return $foo . "bar";
    }
}


#### mock B

class B {
    public function exampleMethod($foo) {
        switch($foo) {
            case 'test':
                return 'testbar';
            default:
                throw new Exception('Unexpected input for stub function ' . __FUNCTION__);
        }
    }
}


#### test A

class TestA extends PHPUnit_Extensions_Database_TestCase {
    protected function setUp() 
    {
        // include mocks specific to this test
        set_include_path(get_include_path() . PATH_SEPARATOR . 'tests/A/mocks');

        // require the class we are testing
        require_once 'a.class.php';     

        $this->a = new A();
    }

    public function testMyMethod() {
        $this->assertEquals('testbar', $a->myMethod('test'));
    }
}

#### test B

class TestB extends PHPUnit_Extensions_Database_TestCase {
    protected function setUp()
    {
        // include mocks specific to this test
        set_include_path(get_include_path() . PATH_SEPARATOR . 'tests/B/mocks');

        // require the class we are testing
        // THIS FAILS WITH: 'PHP Fatal error:  Cannot redeclare class B'
        require_once 'b.class.php';

        $this->b = new AB();
    }   
}

【问题讨论】:

    标签: php unit-testing testing mocking phpunit


    【解决方案1】:

    我认为您需要在独立进程中运行测试

    您要么提供执行测试的参数:“--process-isolation”,要么设置$this->processIsolation = true;

    【讨论】:

    • 谢谢,这看起来正是我想要的,虽然我还没有测试过。一位同事刚刚向我展示了您也可以通过在 PHPUnit xml 配置文件中将 processIsolation 设置为“true”来实现此目的。
    • 哦,是的,我很抱歉我只是做了一个快速的研究,但这个方法是正确的。我编辑了我的答案:)
    【解决方案2】:

    如果您由于某种原因(并且有一些有效的原因)不能使用 PHPUnit 模拟 API(或 Mockery),那么您需要自己创建模拟类。

    模拟类应该具有与真实类相同的“类型”(以便类型提示仍然有效),因此您应该扩展真实类:

    #### mock B
    
    class Mock_B extends B {
    

    这也适用于您不能在 PHP 中拥有 2 个具有相同名称的类的事实 :)

    【讨论】:

      【解决方案3】:

      你也可以在声明模拟时使用命名空间

      【讨论】:

        猜你喜欢
        • 2011-12-06
        • 2015-02-12
        • 1970-01-01
        • 2021-10-10
        • 2020-12-09
        • 1970-01-01
        • 2013-08-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多