Как часто вы пишите велосипеды? Можно я тоже тут рядом пристроюсь? Но дело в том, что мой велосипед, он особенный. Казалось бы, простенький, трехколесненький, склепанный всего за один день. Но есть одна хитрость — он работает на движке от болида. Что он умеет?
А смотрите:
<?php
$class = MySingleton::getInstance();
var_dump($class instanceof MySingleton); // => (bool)true
var_dump($class instanceof DOMDocument); // => (bool)false
// а теперь немного магии
test::double('MySingleton', ['getInstance' => new DOMDocument]);
var_dump($class instanceof MySingleton); // => (bool)false
var_dump($class instanceof DOMDocument); // => (bool)true
?>
О нет, мы изменили синглтон! Мы переопределили статический метод. Как же теперь жить?
Но вопрос теперь в другом: как мы жили до этого?
Встречайте AspectMock. Самый простой, но самый мощный фреймворк для моков и стабов на PHP.
Ваш новый суперпростой помощник в тестировании. Основан на Go AOP от NightTiger.
Итак, идея AspectMock достаточно простая: позволить тестировать всё то что в PHP ранее считалось «плохими практиками» по причине невозможности тестирования. Статические методы, синглтоны, методы класса — всё это теперь поддается изменениям в реальном времени. Можно проверить вызов любого метода. Вот пример:
<?php
class UserService {
function createUserByName($name)
{
$user = new User;
$user->setName($name);
$user->save();
}
}
?>
Увы, но мы не можем сделать юнит тест для этого метода классическими средствами. Чтобы мы не делали, а будет вызван метод save(), который обратится в базу данных. Впрочем, в AspectMock это вообще не проблема.
<?php
function testUserCreate()
{
$user = test::double('User', ['save' => null]));
$service = new UserService;
$service->createUserByName('davert');
$this->assertEquals('davert', $user->getName());
$user->verifyInvoked('save');
}
?>
Метод save был вызван, но он был заменен пустышкой. Мы довольны, база цела, имя пользователя присвоено. Покрытие 100%, изоляция присутствует.
Зачем это всё?
В PHP сложилась странная практика считать любой нетестируемый код плохим. Пример выше показывает, что использование паттерна ActiveRecord продуцирует такой нетестируемый код. Плохой ли он? Ну неправда ведь. Хороший паттерн, реализован во многих ORM на разных яхыках.
Практика странная тем, что «тестируемость» кода определяется только техническими ограничениями самого языка PHP.
И потому, прежде чем писать какой либо код, нужно сразу держать в голове: как мы будем это тестировать. И обязательно использовать Dependency Injection. Обязательно.
А теперь на секундочку давайте представим, что почти любой ООП код на PHP поддается юнит-тестировнию. Может лучше сосредоточимся на читабельности кода и его эффективности, вместо того, чтобы плодить лишние сервисы, инжектить их, а потом создавать на них моки многострочными конструкциями в PHPUnit?
Впрочем, реальность такова, что большинство разработчиков относятся к этому вопросу проще. Они вообще не пишут юнит тесты.
AspectMock позволит вам сосредоточиться на написании эффективного кода. Конечно, вы должны соблюдать правильную архитектуру, правильные стандарты, следить за использованием зависимостей, но вам больше не стоит себя искусственно ограничивать техническими возможностями PHP. Как говорил один замечательный человек: «Ничто не истина. Всё дозволено.»
Как это работает?
Сам AspectMock очень простой: всего 8 файлов. Там практически нечего изучать. Зато Go AOP, о котором вы уже могли читать на Хабре, предоставляет отличную платформу для того, чтобы встраиваться в любые методы приложения посредством pointcut'ов. На основе наших предпочтений мы можем подменять их своими пустышками, а также регистрировать их выполнение. Go AOP в реальном времени создает прокси классы и встраивает их в иерархию. Работает посредством изменения автолоадинга. Совместим со всеми популярными фреймворками: Symfony2, Zend Framework 2, Yii, Laravel. Если вы ещё не знакомы с Go Aop, очень рекомендую с ним поиграться.
Где применять AspectMock?
Ну точно что не в продакшне. AspectMock создан исключительно для тестирования, он устанавливается через композер и работает в PHPUnit и Codeception. Насколько AspectMock стабилен? Ровно настолько же, насколько и сам Go AOP. Как понимаете, проект пока очень даже экспериментальный. Текущая версия 0.1.0. Самое сложное — первичная установка. Попробуйте, если же у вас всё завелось, дальше должен работать с пол пинка.
Будут очень интересны ваши отзывы. Спасибо за внимание.
AspectMock обитает на GitHub`е
Автор: Davert