PHPUnit: простой синтаксис для создания mock-объектов



  1. почему не mockery
  2. основное преимущество — синтаксис
  3. моки — нативные
  4. создание stub-объектов
  5. мокирование свойств класса
  6. mock injection
  7. удобные методы для работы с Reflection
  8. как добавить в проект


Почему не Mockery


PHPUnit — стандартный, наиболее распространенный фреймворк для написания unit-тестов в мире PHP. Ничего удивительного — он хорошо справляется с возложенными на него обязанностями. Но если говорить о стандартном синтаксисе создания mock-объектов, многие люди жалуются на его некоторую громоздкость. Они предлагают различные плагины для создания моков, такие как Mockery (я понимаю, что это не просто плагин).

Однако, я уверен что PHPUnit обладает достаточно хорошо развитой системой для создания моков, чтобы продолжать его использовать. PHPUnit активно развивается: не так давно были добавлены мокирование трейтов, мокирование несуществующих классов — для TDD.

И этот проект XPMock — это способ упростить синтаксис создания моков. Нужно подчеркнуть, что XPMock не создает собственные моки, и не делает никаких оберток над моками PHPUnit. XPMock вызывает те же самые методы PHPUnit, создавая тем самым те же самые моки, только несколько проще.

Основное преимущество — синтаксис


Стандартный синтаксис для создания мока (объекта-заглушки с тремя методами) в PHPUnit выглядит так:

$mock = $this->getMockBuilder('MyClass')
   ->setMethods(['getBool', 'getNumber', 'getString'])
   ->disableOriginalConstructor()
   ->getMock();
$mock->expects($this->any())
   ->method('getBool')
   ->will($this->returnValue(true));
$mock->expects($this->any())
   ->method('getNumber')
   ->will($this->returnValue(1));
$mock->expects($this->any())
   ->method('getString')
   ->will($this->returnValue('string'));


Те, кто мокируют большую часть зависимостей в unit-тестах, замечают, что код теста очень быстро разрастается именно из-за таких конструкций.

Если же использовать XPMock, то создание мока становится значительно короче:

$this->mock('MyClass')
   ->getBool(true)
   ->getNumber(1)
   ->getString('string')
   ->new();


Моки — нативные


XPMock
— не создает свои собственные моки
— не делает дополнительных оберток над моками PHPUnit
— поддерживает все нативные конструкции PHPUnit

Например,

$mock->getNumber($this->once())


это то же самое, что написать

$mock->expects($this->once())
    ->method('getNumber')
    ->will($this->returnValue(null))


Другие примеры короткой записи часто используемых конструкций можно посмотреть здесь: github.com/ptrofimov/xpmock

Создание stub-объектов


Stub-объекты, или объекты-заглушки, — это мок-объекты, у которых все методы по умолчанию перекрывают реальные методы и возвращают null. В PHPUnit в синтаксисе нет деления на моки, стабы и иже с ними. Все искусственные объекты для тестирования создаются с помощью getMock или getMockBuilder.

В XPMock включен специальный метод stub который вернет мок, у которого все методы по умолчанию возвращают null. Это и улучшает читаемость кода, и избавляет от необходимости вызывать метод setMethods при создании реальных моков.

Мокирование свойств класса


PHPUnit не умеет мокировать свойства класса. XPMock — тоже. Однако он предоставляет удобные методы задания свойств у создаваемого мока через Reflection. Указанные при создании мока присваивания будут выполнены сразу после вызова метода-конструктора new.

$this->mock(‘MyClass’)
     ->__set(‘property’, $value)
     ->new();


Можно использовать и привычный синтаксис задания свойств без использования магических методов.

Mock injection


Очень часто при создании моков их нужно сразу же внедрить в другие объекты. Например, создать instance синглтона и внедрить ссылку на него в статическую переменную instance того же класса. Обычно это делается через Reflection. XPMock и здесь предоставляет удобный синтаксис для таких действий.

$this->mock(‘MyClass’)
    ->injectTo($object, ‘property’)
    ->new();


Удобные методы для работы с Reflection


XPMock предоставляет и общий короткий синтаксис для работы с объектами через Reflection.

Например, чтобы получить закрытое свойство объекта, обычно нужно писать так:

$property = new \ReflectionProperty(‘MyClass’, ‘property’);
$property->setAccessible(true);
$value = $property->getValue($object);


С XPMock можно это делать так:

$value = $this->reflect('MyClass')->property;


Подобным синтаксисом можно получать значения закрытых/открытых статических/нестатических свойств объектов, а также вызывать закрытые методы.

Как добавить в проект


XPMock легко встраивается в существующие тесты и не мешает работать нативному синтаксису создания моков PHPUnit.

Вариант 1. Добавить трейт XPMock к существующему тесту (подходит для PHP>=5.4)

class MyTestCase extends \PHPUnit_Framework_TestCase
{
    use \Xpmock\TestCaseTrait;
}


Вариант 2. Унаследовать класс теста от соответстующего класса XPMock (подходит для PHP>=5.3)

class MyTestCase extends \Xpmock\TestCase
{
}


Добавить XPMock в проект очень просто — через менеджер зависимостей composer. Инструкция по установке из 2-х шагов здесь — github.com/ptrofimov/xpmock#installation
  • +19
  • 17,5k
  • 9
Поделиться публикацией

Комментарии 9

    +1
    Хм, интересная вещь. Но она просто добавляет немного сахара к вызовам или я что-то упускаю?
    Кстати, github.com/phpspec/prophecy не смотрели?
      +1
      Из названия статьи видно, что это упрощенный синтаксис, как вы говорите — сахар.
        0
        Да, конечно, видел и prophecy. Как и Mockery, достойная разработка.
        0
        В PHP уже достаточно много возможностей делать моки. Это и 3 упомянутых фреймворка, и довески как XPMock.
        А всё равно синтаксис совершенно невнятен :(

        Я кстати тоже свою поделку поделку на эту тему делал, но ограничился стабами.
          0
          Phake b Mockery смотрятся как-то естественнее
          github.com/padraic/mockery
          phake.digitalsandwich.com/docs/html/preface.html
            0
            спасибо.
              0
              естественнее — вы имеете в виду more human language like?
            0
            также очень удобно использовать Sylph для стабов:
            github.com/dracony/Sylph

            Он налету создает класс из описанных в масиве пропертей (включая методы).
              0
              Насчет динамического создания объектов: у меня тоже есть своя поделка)))

              Создавать объекты в точности как в JS:

              github.com/ptrofimov/jslikeobject

              Или вот вообще сумасшедший эксперимент:

              github.com/ptrofimov/graphobject

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое