Статьи

Смоделируйте ваши тестовые зависимости с помощью насмешек

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

Что такое издевательство?

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

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

  1. При выполнении юнит-тестов лучше всего изолировать класс. Вы не хотите, чтобы другой класс или служба мешали вашему модульному тесту.
  2. Объект еще не существует. Вы можете сначала создать тесты, а затем построить конечные объекты.
  3. Макет объекта обычно быстрее, чем подготовка всей базы данных для вашего теста.

При запуске модульных тестов вы, вероятно, используете PHPUnit. PHPUnit поставляется с некоторыми способностями насмешки по умолчанию, как вы можете видеть в документации . В этой статье, написанной Jeune Asuncion, вы можете прочитать больше о насмешках в целом и о способностях насмешки из PHPUnit.

В этой статье мы погрузимся в Mockery , библиотеку, созданную Падрайком Брейди. Мы создадим температурный класс, в который будет добавлена ​​текущая метеослужба.

Настроить

Давайте начнем с настройки нашего проекта. Мы начнем с файла composer.json который содержит следующее содержимое. Это обеспечит доступность насмешек и PHPUnit.

 { "name": "sitepoint/weather", "license": "MIT", "type": "project", "require": { "php": ">=5.3.3" }, "autoload": { "psr-0": { "": "src/" } }, "require-dev": { "phpunit/phpunit": "4.1.*", "mockery/mockery": "0.9.*" } } 

Мы также создаем файл конфигурации PHPUnit с именем phpunit.xml

 <phpunit> <testsuite name="SitePoint Weather"> <directory>src</directory> </testsuite> <listeners> <listener class="\Mockery\Adapter\Phpunit\TestListener" file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"> </listener> </listeners> </phpunit> 

Важно определить этого слушателя. Без слушателя методы, такие как once() , twice() и times() не будут выдавать ошибку, если они не используются правильно. Подробнее об этом позже.

Я также создал 2 каталога. Каталог src в котором хранятся мои классы, и каталог tests для хранения наших тестов. В каталоге src я создал путь SitePoint\Weather .

Мы начнем с создания WeatherServiceInterface . Наш несуществующий сервис погоды будет реализовывать этот интерфейс. В этом случае мы предоставляем только метод, который даст нам температуру в градусах Цельсия.

 namespace SitePoint\Weather; interface WeatherServiceInterface { /** * Return the Celsius temperature * * @return float */ public function getTempCelsius(); } 

Итак, у нас есть сервис, который обеспечивает нам температуру в градусах Цельсия. Я хотел бы получить температуру в градусах Фаренгейта. Для этого я создаю новый класс с именем TemperatureService . Эта служба будет вводить метеослужбу. Кроме того, мы также определяем метод, который преобразует температуру по Цельсию в градусы Фаренгейта.

 namespace SitePoint\Weather; class TemperatureService { /** * @var WeatherServiceInterace $weatherService Holds the weather service */ private $weatherService; /** * Constructor. * * @param WeatherServiceInterface $weatherService */ public function __construct(WeatherServiceInterface $weatherService) { $this->weatherService = $weatherService; } /** * Get current temperature in Fahrenheit * * @return float */ public function getTempFahrenheit() { return ($this->weatherService->getTempCelsius() * 1.8000) + 32; } } 

Создать юнит-тест

У нас есть все для настройки нашего модульного теста. Мы создаем класс TemperatureServiceTest в каталоге tests . В этом классе мы создаем метод testGetTempFahrenheit() который будет проверять наш метод по Фаренгейту.

Первым шагом в этом методе является создание нового объекта TemperatureService . В тот момент, когда мы это делаем, наш конструктор будет запрашивать объект с реализованным WeatherServiceInterface . Поскольку у нас пока нет такого объекта (и он нам не нужен), мы собираемся использовать Mockery, чтобы создать для нас фиктивный объект. Давайте посмотрим, как будет выглядеть метод, когда он будет полностью завершен.

 namespace SitePoint\Weather\Tests; use SitePoint\Weather\TemperatureService; class TemperatureServiceTest extends \PHPUnit_Framework_TestCase { public function testGetTempFahrenheit() { $weatherServiceMock = \Mockery::mock('SitePoint\Weather\WeatherServiceInterface'); $weatherServiceMock->shouldReceive('getTempCelsius')->once()->andReturn(25); $temperatureService = new TemperatureService($weatherServiceMock); $this->assertEquals(77, $temperatureService->getTempFahrenheit()); } } 

Мы начнем с создания фиктивного объекта. Мы сообщаем Mockery, какой объект (или интерфейс) мы хотим смоделировать. Второй шаг — описать, какой метод будет вызываться для этого фиктивного объекта. В shouldReceive() мы определяем имя метода, который будет вызван.

Мы определяем, сколько раз этот метод будет вызван. Мы можем использовать once() , twice() и times(X) . В этом случае мы ожидаем, что он будет вызван только один раз. Если он не вызывается или вызывается слишком много раз, модульный тест не пройден.

Наконец, мы определяем в andReturn() , какое значение будет возвращено. В этом случае мы возвращаемся 25 . У насмешек также есть методы возврата, такие как andReturnNull() , andReturnSelf() и andReturnUndefined() . Издевательство также может отбрасывать исключения, если вы этого ожидаете.

Теперь у нас есть фиктивный объект, и мы можем создать наш объект TemperatureService и выполнить тест, как обычно. 25 по Цельсию — это 77 по Фаренгейту, поэтому мы проверяем, получим ли мы 77 обратно от нашего getTempFahrenheit() .

Если вы запустите vendor/bin/phpunit tests/ в своем корне, вы получите зеленый свет от PHPUnit, указывающий, что все идеально.

продвинутый

Пример выше был довольно прост. Нет параметров, только один простой вызов. Давайте сделаем все немного сложнее.

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

 /** * Return the Celsius temperature by hour * * @param $hour * @return float */ public function getTempByHour($hour); 

Мы хотели бы знать, какая средняя температура между 0:00 и 6:00 ночью. Для этого мы создаем новый метод в нашем TemperatureService который вычисляет среднюю температуру. Для этого мы получаем 7 температур из нашего WeatherService и вычисляем среднее значение.

 /** * Get average temperature of the night * * @return float */ public function getAvgNightTemp() { $nightHours = array(0, 1, 2, 3, 4, 5, 6); $totalTemperature = 0; foreach($nightHours as $hour) { $totalTemperature += $this->weatherService->getTempByHour($hour); } return $totalTemperature / count($nightHours); } 

Давайте посмотрим на наш метод испытаний.

 public function testGetAvgNightTemp() { $weatherServiceMock = \Mockery::mock('SitePoint\Weather\WeatherServiceInterface'); $weatherServiceMock->shouldReceive('getTempByHour') ->times(7) ->with(\Mockery::anyOf(0, 1, 2, 3, 4, 5, 6)) ->andReturn(14, 13, 12, 11, 12, 12, 13); $temperatureService = new TemperatureService($weatherServiceMock); $this->assertEquals(12.43, $temperatureService->getAvgNightTemp()); } 

Еще раз насмехаемся над интерфейсом и определяем метод, который будет вызываться. Далее мы определяем, сколько раз этот метод будет вызван. Мы использовали once() в предыдущем примере, теперь мы используем times(7) чтобы указать, что мы ожидаем, что этот метод будет вызван 7 раз. Если метод не вызывается ровно 7 раз, тест не пройден. Если вы не определили прослушиватель в phpunit.xml конфигурации phpunit.xml , вы не получите уведомление об этом.

Далее мы определим метод with() . В методе with вы можете определить ожидаемые параметры. В этом случае нас ожидают 7 разных часов.

И наконец, у нас есть метод andReturn() . В этом случае мы указали 7 возвращенных значений. Если вы определяете меньшее количество возвращаемых значений, последнее доступное возвращаемое значение будет повторяться каждый раз.

Конечно, издевательство может сделать намного больше. Для полного руководства и документации, я рекомендую вам взглянуть на страницу Github .

Если вас интересует код из вышеприведенного проекта, вы можете взглянуть на эту страницу Github .

Вывод

С помощью PHPUnit вы уже можете макетировать объекты. Однако вы также можете использовать Mockery, как описано в приведенных выше примерах. Если вы проводите модульное тестирование своих классов и не хотите, чтобы другие классы влияли на ваш тест, насмешка может помочь вам с легкостью. Если вы действительно хотите проводить функциональные тесты, лучше взглянуть, если вы, конечно, можете интегрировать реальную сделку. Вы в настоящее время используете насмешку над PHPUnit и думаете о переходе на Mockery? Хотели бы вы видеть больше и больше примеров насмешек в следующей статье? Позвольте мне знать в комментариях ниже.