Ошибка «Не удается переопределить класс» при попытке смоделировать зависимости в PHPUnit - PullRequest
3 голосов
/ 24 марта 2012

Я реализую модульное тестирование с помощью 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();
    }   
}

Ответы [ 3 ]

1 голос
/ 24 марта 2012

Если вы по какой-то причине (и есть некоторые действительные) не можете использовать API-интерфейс для пересмотра PHPUnit (или Mockery), тогда вам нужно создать классы-имитаторы самостоятельно.

Модулируемый класс должен иметь тот же "Тип", что и реальный (так, чтобы хинтинг типа все еще работал), и по этой причине вы должны расширить реальный:

#### mock B

class Mock_B extends B {

Это также работает вокруг того факта, что в PHP не может быть 2 классов с одинаковыми именами:)

1 голос
/ 24 марта 2012

Я думаю, вам нужно запустить свои тесты в изолированных процессах

Либо вы указываете аргумент для выполнения теста: "--process-изоляция", либо вы устанавливаете $this->processIsolation = true;

0 голосов
/ 26 марта 2012

Вы также можете использовать пространства имен при объявлении фиктивных

...