kix

Mockery: вкуснота

Поскольку я тут продолжаю писать про тестирование, не могу не упомянуть о прекрасной библиотеке Mockery. Это отличная реализация мок-объектов, которой в разы удобнее пользоваться, в сравнении со средствами PHPUnit, по крайней мере.

Чтобы получить Mockery в проект, надо в блок require в composer.json вписать вот такую вот строчку:

1
2
3
    "require": {
        "mockery/mockery": "dev-master"
    }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
use \Mockery as m;

$geocoded = new \Geocoder\Result\Geocoded();
$geocoded->fromArray(array(
    'latitude'  => 12,
    'longitude' => 34,
    'city'      => 'Test city',
));

$geocoderInterfaceMock = m::mock('\Geocoder\GeocoderInterface')
                          ->shouldReceive('geocode')
                          ->with('46.48.48.87')
                          ->andReturn($geocoded)
                          ->getMock();

$geocoderMock = m::mock('\Geocoder\GeocoderInterface')
                 ->shouldReceive('using')->once()->with(m::type('string'))
                 ->andReturn($geocoderInterfaceMock)->getMock();

В приведенном блоке кода я использую весь тот функционал Mockery, который в приципе необходим в большинстве тестов.

Обратите внимание, какой симпатичный (и человекопонятный!) у Mockery API: все методы называются так, как они и должны называться, все нужное выведено в статические функции у легкодоступного класса, словом — сказка, а не моки.

Собственно, что к чему в коде.

Метод mock('\Имя\Класса\Как\Строка') дает нам мок-объект, над которым мы впоследствии можем всячески издеваться. shouldReceive('имяМетода') добавляет в мок ожидание и возвращает его в стиле fluent interface. Этот самый fluent interface позволяет настроить ожидание.

Метод ожидания with() принимает как точные значения параметров, так и Matcher. Matcher по сути является вот таким куском кода:

Mockery\Matcher\Type
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

namespace Mockery\Matcher;

class Type extends MatcherAbstract
{
    /**
     * Check if the actual value matches the expected.
     *
     * @param mixed $actual
     * @return bool
     */
    public function match(&$actual)
    {
        $function = 'is_' . strtolower($this->_expected);
        if (function_exists($function)) {
            return $function($actual);
        } elseif (is_string($this->_expected)
        && (class_exists($this->_expected) || interface_exists($this->_expected))) {
            return $actual instanceof $this->_expected;
        }
        return false;
    }

Видим, что путем нехитрого колдовства тут проверяется входящее значение, и оно может принадлежать либо к одному из стандартных типов (тех, которые проверяются методами is_bool, is_string и т.д.), либо являться экземпляром класса или экземпляром реализации какого-либо интерфейса, либо вовсе быть функцией.

Также в создании $geocoderMock я после вызова shouldReceive() указываю, что метод using в имитируемом классе должен быть вызван один раз. И делаю это простым вызовом \Mockery\Expectation::once(), что несомненно куда как приятнее, чем стиль PHPUnit. А вот кстати полный набор методов для указания количества вызовов.

С методом andReturn(), думаю, все понятно — он принимает очередь параметров. В моем случае очередь состоит всегда из одного параметра, потому что тестируется достаточно простой функционал, не требующий особых танцев с бубном.

Ну и небольшой нюанс в том, что после объявления ожиданий мока последним значением, вернувшимся в нашем fluent interface, является ожидание, а не сам мок, поэтому в конце вызова нужно вызвать еще и метод \Mockery\Expectation::getMock().

Конечно, это ваше дело, чем пользоваться — Mockery или средствами, встроенными в PHPUnit, но все же мне кажется, что эта библиотека более чем достойна упоминания.

comments powered by Disqus