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