Макетные объекты в PHPUnit для эмуляции вызовов статических методов?
Вопрос
Я пытаюсь протестировать класс, который управляет доступом к данным в базе данных (вы знаете, CRUD, по сути).Библиотека DB, которую мы используем, имеет API, в котором вы сначала получаете объект table с помощью статического вызова:
function getFoo($id) {
$MyTableRepresentation = DB_DataObject::factory("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
... вы уловили идею.
Мы пытаемся протестировать этот метод, но издеваемся над материалом DataObject, так что (а) нам не нужно фактическое подключение к БД для теста, и (б) нам даже не нужно включать DB_DataObject lib для теста.
Однако в PHPUnit я, похоже, не могу получить $this->getMock(), чтобы соответствующим образом настроить статический вызов.У меня есть...
$DB_DataObject = $this->getMock('DB_DataObject', array('factory'));
...но в тесте по-прежнему указан неизвестный метод "factory".Я знаю, что он создает объект, потому что до этого он сказал, что не может найти DB_DataObject .Теперь это возможно.Но, никакого метода?
Что я действительно хочу сделать, так это иметь два макетных объекта, один из которых также возвращается для объекта table.Итак, мне нужно не только указать, что factory является статическим вызовом, но и то, что он возвращает какой-то указанный другой макет объекта, который я уже настроил.
Я должен упомянуть в качестве предостережения, что я сделал это в SimpleTest некоторое время назад (не могу найти код), и это сработало нормально.
Что это дает?
[ОБНОВЛЕНИЕ]
Я начинаю понимать, что это как-то связано с expects()
Решение
Я согласен с вами обоими, что было бы лучше не использовать статический вызов.Однако, я думаю, я забыл упомянуть, что DB_DataObject является сторонней библиотекой, и статический вызов их лучшая практика для использования их кода, а не нашего.Существуют другие способы использования их объектов, которые включают непосредственное построение возвращаемого объекта.Это просто оставляет эти проклятые инструкции include / require в любом файле класса, использующем этот класс DB_DO.Это отстой, потому что тесты сломаются (или просто не будут изолированы), если вы тем временем пытаетесь высмеять класс с тем же именем в своем тесте - по крайней мере, я так думаю.
Другие советы
Если вы не можете изменить библиотеку, измените свой доступ к ней.Преобразуйте все вызовы DB_DataObject::factory() в метод экземпляра в вашем коде:
function getFoo($id) {
$MyTableRepresentation = $this->getTable("mytable");
$MyTableRepresentation->get($id);
... do some stuff
return $somedata
}
function getTable($table) {
return DB_DataObject::factory($table);
}
Теперь вы можете использовать частичный макет тестируемого класса и попросить GetTable() вернуть макет объекта table.
function testMyTable() {
$dao = $this->getMock('MyTableDao', array('getMock'));
$table = $this->getMock('DB_DataObject', ...);
$dao->expects($this->any())
->method('getTable')
->with('mytable')
->will($this->returnValue($table));
$table->expects...
...test...
}
Это хороший пример зависимости в вашем коде - дизайн сделал невозможным внедрение в макет, а не в реальный класс.
Моим первым предложением было бы попытаться реорганизовать код, чтобы использовать экземпляр, а не статический вызов.
Чего не хватает (или нет?) в вашем классе DB_DataObject, так это средства настройки для передачи подготовленного объекта db перед вызовом заводского метода.Таким образом, вы можете передать макет или пользовательский объект базы данных (с тем же интерфейсом), если возникнет необходимость.
В вашей тестовой настройке:
public function setUp() {
$mockDb = new MockDb();
DB_DataObject::setAdapter($mockDb);
}
Метод factory() должен возвращать издевательский экземпляр DB.Если он еще не интегрирован в ваш класс, вам, вероятно, также придется провести рефакторинг метода factory(), чтобы заставить его работать.
Требуется ли вам / включая файл класса для DB_DataObject в вашем тестовом примере?Если класс не существует до того, как PHPUnit попытается издеваться над объектом, вы можете получить ошибки, подобные этой.
С расширением PHPUnit MockFunction плюс runkit вы также можете имитировать статические методы.Будьте осторожны, потому что это обезьяний патч, и поэтому его следует использовать только в крайних случаях.Не заменяет хорошие методы программирования.