Макетные объекты в PHPUnit для эмуляции вызовов статических методов?

StackOverflow https://stackoverflow.com/questions/344315

  •  19-08-2019
  •  | 
  •  

Вопрос

Я пытаюсь протестировать класс, который управляет доступом к данным в базе данных (вы знаете, 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 вы также можете имитировать статические методы.Будьте осторожны, потому что это обезьяний патч, и поэтому его следует использовать только в крайних случаях.Не заменяет хорошие методы программирования.

https://github.com/tcz/phpunit-mockfunction

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top