Статьи

Обзор издевательства

Mockery — это фальшивый объектный фреймворк (точнее Test Double framework) от @padraicb , независимый от тестовых фреймворков, таких как PHPUnit. Его можно использовать для быстрой подготовки Mocks, Stubs и других Test Double для использования внутри ваших юнит-тестов.

Я пробовал Mockery с помощью установки PEAR, и должен сказать, что его выразительность выше, чем у системы насмешки PHPUnit. Тем не менее, он может быть слишком мощным для эффективного использования.

Первый взгляд

Документация для Mockery содержится в файле README. Официальная документация не великолепна и для PHPUnit, но есть много неофициальных руководств (включая практические серии), ориентированных на PHPUnit в качестве стандарта.

Однако Mockery не полностью заменяет PHPUnit, так как это всего лишь фальшивый фреймворк; если вам не подходит генерация макетов, заглушек и других дубликатов тестов с помощью PHPUnit, естественным выбором будет принятие более мощного механизма имитации, чем связанного.

У насмешек нет никаких других зависимостей, кроме сильно рекомендованного Hamcrest, который также можно установить через PEAR. Эта версия Hamcrest является портом соответствия Hamcrest для языка PHP. Насмешливый Api в основном статичен, но и PHPUnit находится под прикрытием. Я не сталкивался с проблемами, вытекающими из этого процедурного подхода.

Уникальные черты

Издевательство может создавать Test Doubles для конкретных классов, абстрактных классов или интерфейсов. В частности, он имеет некоторые функции, которых нет в PHPUnit, и это делает его интересным:

  • альтернативные ожидания, которые применяются только при использовании определенного набора параметров. Это позволяет создавать умные объекты, которые возвращают разные результаты при разных вызовах.
  • запись ожидаемых звонков на макет вместо определения их через API.
  • издевательство над несуществующими методами , которое находится на границе потенциально вредных функций. Тем не менее, он может служить для разработки с макетом до извлечения интерфейса. Интерфейс, который не существует, не должен синхронизироваться с тестами во время этих трудных начальных этапов, а должен создаваться только перед следующей фиксацией.

Еще один момент для Mockery заключается в том, что он не пытается быть слишком умным: он не клонирует ожидаемые параметры, как PHPUnit, что позволяет вам проверять их идентичность с помощью ===.

От великой силы приходит великая ответственность

В некоторых аспектах издевательство слишком жесткое для меня: оно может высмеивать статические методы и публичные свойства или перехватывать функции автозагрузки для насмешки над новыми операторами. Я не уверен, что эти функции полезны, кроме тестирования унаследованного кода, и они могут позволить вам дольше следовать проблемному дизайну.

Например, рассмотрим насмешливые цепочки Деметры, в этом примере взятые из документации:

$mock = \Mockery::mock('CaptainsConsole');
$mock->shouldReceive('foo->bar->zebra->alpha->selfDestruct')->andReturn('Ten!');

Производственный код теперь может вызывать $ object-> foo () -> bar () -> zebra () -> alpha () -> selfDestruct (). Тем не менее, код, который предполагает так много о графе окружающих объектов, очень связан с другими классами и будет ломаться всякий раз, когда его окружение изменяется; в этом случае мощь инфраструктуры тестирования препятствует обратной связи, которую могут дать тесты: избегайте этой цепочки вызовов.

В этом смысле Мокери чувствует себя стартапом, который продает машину, которая может очень быстро заводиться, чтобы грабители могли избежать полиции. ?

Покажи мне код

Api of Mockery — это волшебство: в качестве первого аргумента mock () можно передать все, например, имя, имя класса или массив с фиксированным ожиданием. То же самое относится к mustReceive (), который принимает цепочки даже Demeter, как вы видели.

Снова в Api мы находим мощные функции, такие как частичные имитации, реализованные в виде прокси для реального объекта. Вот пример того, что я нашел наиболее полезным с точки зрения объектно-ориентированного дизайна:

<?php
use \Mockery as m;

class MockeryExploratoryTest extends PHPUnit_Framework_TestCase
{
    public function testMockASimpleMethodCalledMultipleTimes()
    {
        $service = m::mock('PlanetColorService');
        $service->shouldReceive('getColor')->times(2)->andReturn('blue', 'red');
        $this->assertEquals('blue', $service->getColor('Earth'));
        $this->assertEquals('red', $service->getColor('Mars'));
    }

    public function testMockAnInterface()
    {
        $service = m::mock('ColorService');
        $service->shouldReceive('getColor')->andReturn('blue');
        $this->assertEquals('blue', $service->getColor('water'));
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testClassicThrowingOfAnException()
    {
        $service = m::mock('PlanetColorService'); 
        $service->shouldReceive('getColor')->withAnyArgs()->andThrow(new InvalidArgumentException);
        $service->getColor('Earth');
    }

    /**
     * PHPUnit cannot do this.
     */
    public function testMockWithDifferentReturnValuesForDifferentExpectations()
    {
        $service = m::mock('PlanetColorService');
        $service->shouldReceive('getColor')->with('Earth')->andReturn('blue');
        $service->shouldReceive('getColor')->with('Mars')->andReturn('red');
        $this->assertEquals('blue', $service->getColor('Earth'));
        $this->assertEquals('red', $service->getColor('Mars'));
    }

    /**
     * It neither can do this.
     */
    public function testVerifiesTheOrderOfCallsOnDifferentMethods()
    {
        $service = m::mock('PlanetIdentificator');
        $service->shouldReceive('getPlanet')->withAnyArgs()->ordered();
        $service->shouldReceive('getMoon')->withAnyArgs()->ordered();
        $service->getPlanet('blue');
        $service->getMoon('Earth', 'yellow');
    }

    public function teardown()
    {
        m::close();
    }
}

interface ColorService
{
    public function getColor($planet);
}

class PlanetColorService implements ColorService
{
    public function getColor($planet) {}
}

interface PlanetIdentificator
{
    public function getPlanet($color);
    public function getMoon($planet, $color);
}

Я использовал следующую загрузку:

<?php
require_once 'Mockery/Loader.php';
require_once 'Hamcrest/hamcrest.php';
$loader = new \Mockery\Loader;
$loader->register();

Выводы

С пуристской шляпой я бы сказал, что в случае, если вы должны создать свои тестовые двойники с каркасом, потому что они слишком сложны, чтобы издеваться вручную, вы уже в опасности.

Однако Mockery позволяет вам создавать макеты быстрее, и это будет очень хорошо для тестирования старого кода. Это также требует минимальной интеграции с PHPUnit: просто вызов метода в teardown () и автозагрузчик в начальной загрузке.

Таким образом, Mockery добавляет что-то полезное и для TDDing, поскольку сокращает продолжительность цикла Red-Green-Refactor; но имейте в виду, что вы не должны прибегать к определенным взломам нового кода.