benefits of unit-testing and inversion of controll
TRANSCRIPT
Inversion of Control - что такое, подходы, Service Locator, DI, простые
примеры
О пользе тестирования
Жизнь программиста без тестирования
● Пишет много кода и однажды... он перестает работать● Исправление ошибки может быть легкими не замысловатым, но процесс нахождения
ошибки может затягиваться:○ часы, потраченные на отладку и изучение вывода дампов в консоль
● Исправление ошибки может поломать существующие в коде зависимости● Ошибка может проявиться снова на более позднем этапе
В итоге: время разработки и поддержки кода растет с возрастом проекта
О пользе тестирования
Почему разработчики не пишут тесты
Оправдания Причины
● это работает на моем компьютере● предыдущий разработчик не знал об
blah-blah-blah● не могу воспроизвести ошибку● когда я это делал было не так...
● нет времени● бюджет не предусматривает● разработчики не знают как писать тесты● написание тестов после релиза
О пользе тестирования
"Once a test is made, it will always be tested"Michelangelo van Dam
кто-то на MageConf 2012
"Мало плохих тестов лучше, чем вообще без них"
"Every time you wish to dump a variable, write a unit-test for this case."
Chris Hartjes (The Grumpy programmer)
О пользе тестирования
Преимущества тестирования
Поддержка существующего кода Уверенность
● В процессе разработки○ тесты не пройдут, если допущена
ошибка в месте которое они покрывают
● После релиза○ можно быстро узнать, является ли
баг полинным или его "сочинили"○ решение проблемных вопросов не
ломает зависимостей в существующем коде■ если ломает, то тесты фейлятся и
сразу видно где нужно исправить● Долгосрочные проекты
○ упрощает процесс рефакторинга
● Для разработчика○ его код работает○ тесты дают базовое понимание
работы API приложения, упрощают процесс создания документации
● Для менеджера○ проект удачен
● Для продаж○ прибыль
● Для клиента○ доволен, т.к. получает то за что
платит
Unit-тестирование (модульное тестировани) - это автоматизированная, самопроверяющая процедура, которая отвечает за правильность работы модулей.
Модули обычно ассоциируются с классами и задача unit-тестов сводится к проверки на корректность работы интерфейсных (или просто общедоступных) методов модуля (класса).
Проверка класса должна происходить в его изоляции от приложения.
О пользе тестирования
Что есть unit-тестирование
Тест не является модульным, если:
● тест общается с базой данных● тест общается с сетью● тест касается файловой системы● тест запускается одновременно с другим unit-тестом● для его запуска нужно переконфигурировать приложение● тест не тестируется в изоляции от других классов
О пользе тестирования
С чего все началось
Началу эры тестирования положил Кент Бек в своей книге "Extreme programming Explained", в которой он так же изложил первые принцыпы Test-Driven Development (TDD) (разработка через тестирование). Основная мысль автора заключалась в следующем: если тестиование это хорошо, значит программисты должны постоянно тестирвать свой код.
Набор рекомендаций по правилам unit-тестирования и составляет основу методологии TDD.
Одно из определений TDD гласит - что TDD это методика, позволяющая оптимизировать использование модульных тестов.
Задача TDD - достижение балланса между усилиями и результатом.
О пользе тестирования
Итерация в TDD
Тестирование
Проектирование
Реализация / Рефакторинг
ТестированиеТестирование Тестирование
О пользе тестирования
Цена ошибок без unit-тестов
- кол-во багов - траты на проект
запрос на изменение 1
запрос на изменение 2
запрос на изменение N
врем
я на
вне
дрен
ие н
овой
"ф
ичи"
жизнь проекта
коли
чест
во б
агов
- время на новую фичу
жизнь проекта
О пользе тестирования
Цена ошибок c unit-тестами
- кол-во багов - траты на проект
запрос на изменение 1
запрос на изменение 2
запрос на изменение N
врем
я на
вне
дрен
ие н
овой
"ф
ичи"
жизнь проекта
коли
чест
во б
агов
и н
овы
х те
стов
- время на новую фичу
жизнь проекта
- unit-тесты
О пользе тестирования
Скорость разработки приложения
- приект без unit-тестов
врем
я на
вне
дрен
ие н
овог
о ф
ункц
иона
ла
жизнь проекта
- проект с unit-тестами
О пользе тестирования
Проблема модульного тестирования
● Черезмерное покрытие тестами:○ не стоит увлекаться в написании модульных тестов, достаточно что бы они покрывали,
написанную Вами, бизнес логику приложения
● Тесты должны быть простыми:○ тесты, на написание которых у Вас уходит очень много времени - плохие, свозможно стоит их
разбить на несколько тестов или убрать "лишний" функционал из, предварительно разработанного Вами дизайна модуля
● Простота написания тестов зависит от архитектуры Вашего приложения○ чем больше жестких зависимостей, тем хуже
Зависимости
Откуда берутся зависимости
Приложение
PHP extensions / Utils
Массивы данных Ваши классы
уровень контроллеров
уровень сервисов и моделей
з а в и с и м о с т и
Зависимости
Зависимости это плохо ?
"Hard-coded dependencies are bad"Stephan Hachdörfer
Создание жестких зависимостей в классах делает их (классы) не изолированными (ортогональными) и, как следствие, приложение становится трудно-тестируемым.
Тут на помощь приходит техника программирования - Inversion of Control.
Обычно, объекты зависят друг от друга, и это нормальная ситуация. В ООП благородным делом считается избавление объектов от излишних зависимостей, в то же время вообще уйти от них - невозможно! На фоне этого основной задачей разработчика становится контроль зависимостей и их разумное уменьшение.
Inversion of Control
Определения
Inversion of Control - это техника объектно-ориентированного программирования, которая используется для устранение жестких зависимостей в коде, делая компоненты приложения многоразовыми. Так же входит в состав SOLID-принцыпов, которые лежат в основе TDD.
Техника релизует создание объектов "налету" с помощью специального контейнера, определяющего граф зависимостей кассов приложения, и предоставляющего возможность устанавливать зависимости между объектами, которые не доступны на этапе компиляции или при статическом анализе.
Существует несколько техник применения Инверсии зависимостей:● Factory method pattern● Service locator pattern● Dependency injection pattern
○ инъекция в constructor○ инъекция через setter method○ инъекция через интерфейс
● контекстный поиск
Inversion of Control
Service Locator Pattern
диаграмма взята из MSDN
использует
использует
ClassA
ServiceA
ServiceB
испо
льзу
етлокализирует
Locator
ServiceA
ServiceB
ClassA
локализирует
Начальная ситуация● что бы изменить зависимости - нужно
изменить код● зависимости должны быть при компиляции● трудно протестировать класс в изоляции● повторяемый код на создание,
локализацию и управление зависимостями
Применение Шаблона Service Locator● декомпозиция класса● класс не должен ничего знать о сервисах● класс можно протестировать в изоляции● нет логики управления зависимостями
внутри класса● приложение становится модульным,
каждый модуль независим
Inversion of Control
Factory Method Pattern
использует
использует
ClassA
ServiceA
ServiceB
используетFactory
ServiceA ServiceB
ClassA
Начальная ситуация● что бы изменить зависимости - нужно
изменить код● зависимости должны быть при компиляции● трудно протестировать класс в изоляции● повторяемый код на создание,
локализацию и управление зависимостями
Применение Шаблона Factory Method● декомпозиция класса● класс не должен ничего знать о сервисах● класс можно протестировать в изоляции● нет логики управления зависимостями
внутри класса● приложение становится модульным,
каждый модуль независим
фабричный метод
Inversion of Control
Dependency Injection Pattern
диаграмма взята из MSDN
использует
использует
ClassA
ServiceA
ServiceB
создаетBuilder
ServiceA
ClassA
Начальная ситуация● что бы изменить зависимости - нужно
изменить код● зависимости должны быть при компиляции● трудно протестировать класс в изоляции● повторяемый код на создание,
локализацию и управление зависимостями
Применение Шаблона Factory Method● декомпозиция класса● класс не должен ничего знать о сервисах● класс можно протестировать в изоляции● нет логики управления зависимостями
внутри класса● приложение становится модульным,
каждый модуль независим
инстанциируетвнедряет
IServiceA
использует
Пример использования интерфейсной инъекции с помощью Factory Method
Inversion of Control
Простейший пример Dependency Injection (Di)
Базовый класс Отрефакторен с использованием Di
class Foo{ protected $_bar;
public function __construct() { $this->bar = new Bar() }}
class Foo{ protected $_bar;
// инъекция через конструктор public function __construct(Bar $bar) { $this->bar = $bar; }
// инъекция через setter public function setBar(Bar $bar) { $this->bar = $bar; }}
Dependency Injection in frameworks
ZendFramework 2 :: базовый пример
Описание зависимостей Применение Di контейнера
$definitions = array( 'Foo' => array( 'setBar' => array( 'type' => 'Bar', 'required' => true, ) ));
use Zend\Di\Di, Zend\Di\Configuration;
$di = new Di();$config = new Configuration(array( 'definition' => array('class' => $definition)));
$foo = $di->get('Foo');
Больше и более детальные примеры можно посмтреть тут:https://github.com/ralphschindler/Zend_DI-Examples/
Dependency Injection in frameworks
ZendFramework 2 :: Martin Fawler's Movie & Lister
namespace MovieApp {
class Lister { public $dbFinder; public function __construct(DbFinder $dbFinder){ $this->dbFinder = $dbFinder; } }
class DbFinder { public $username, $password = null; public function __construct($username, $password) { $this->username = $username; $this->password = $password; } }
}
namespace { // bootstrap Zend\Loader\StandardAutoloader first
$di = new Zend\Di\Di; $di->instanceManager()->setParameters( 'MovieApp\DbFinder', array( 'username' => 'my-username', 'password' => 'my-password' ) ); $lister = $di->get('MovieApp\Lister');
$works = ( $lister->dbFinder instanceof MovieApp\DbFinder && $lister->dbFinder->username == 'my-username' && $lister->dbFinder->password == 'my-password' );
echo (($works) ? 'Works!' : 'Fails') . PHP_EOL; }
Dependency Injection in frameworks
Symfony 2
class Mailer{ private $transport;
public function __construct() { $this->transport = 'sendmail'; }
// ...}
class NewsletterManager{ private $mailer;
public function __construct(\Mailer $mailer) { $this->mailer = $mailer; }
// ...}
use Symfony\Component\DependencyInjection\ContainerBuilder;use Symfony\Component\DependencyInjection\Reference;
$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');$container ->register('mailer', 'Mailer') ->addArgument('%mailer.transport%');
$container ->register('newsletter_manager', 'NewsletterManager') ->addArgument(new Reference('mailer'));