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