6. Введение в TDD
Стили разработки через
тестирования
• Traditional (традиционный)
• Active (активный)
• Acceptance (приёмочный)
7. Введение в TDD
Traditional (традиционный)
• Разработка только через “сначала-тест-
потом-код”.
• Тесты диктуют архитектуру.
• На первых итерациях получаются простые
“тупые” решения задач.
8. Введение в TDD
Active (активный)
• Разработка только через “сначала-тест-
потом-код”.
• Разработчик продумывает дизайн решения
задачи заранее и потом используя TDD
пытается к нему прийти.
9. Введение в TDD
Acceptance (приёмочный)
• Разработчик пишет один большой тест на всю
задачу вместо того, чтоб писать много
маленьких.
• На каждой итерации этот один тест постоянно
увеличивается.
• Разработчик использует множество Mock-
объектов.
• После того, как функциональность готова,
заменяет Mock-объекты реальными классами.
11. Введение в TDD
Минусы “Активного” стиля
• Бессмысленно потраченное время на
продумывание архитектуры, так как при
TDD часто приходишь к другой архитектуре.
• До решения некоторых задач возможна
ситуация “design deadlock” (архитектурный
тупик): “Традиционный” TDD помогает
выйти из этой ситуации.
12. Введение в TDD
Минусы “Приёмочного” стиля
• Нарушение определения “unit”. Тест
становится бесполезным для поиска и
фикса багов.
• Тест слишком большой и трудно
понимаемый.
• Тест намного меньше влияет на архитектуру
решения, так как “тестируемость” кода не
играет особой роли.
13. Что необходимо знать перед
началом разработки через
тестирование:
• Основные принципы разработки ПО.
• Паттерны проектирования.
• Приёмы рефакторинга кода.
14. Основные принципы разработки ПО
S.O.L.I.D принципы
• SRP: Single Responsibility Principle (Принцип
единственной ответственности)
• OCP: Open Closed Principle (Принцип
открытости-закрытости)
• LSP: Liskov Substitution Principle (Принцип
подстановки Лисков)
• ISP: Interface Segregation Principle (Принцип
сегрегации интерфейса)
• DIP: Dependency Inversion Principle (Принцип
инверсии зависимости)
15. Основные принципы разработки ПО
SRP: Single Responsibility Principle (Принцип
единственной ответственности)
Цель:
• У класса должна быть только одна причина
для изменения. Иными словами класс
должен уметь делать что-то одно.
17. Основные принципы разработки ПО
SRP: Single Responsibility Principle (Принцип
единственной ответственности)
Пример
Example 1:
18. Основные принципы разработки ПО
SRP: Single Responsibility Principle (Принцип
единственной ответственности)
Решение:
19. Основные принципы разработки ПО
OCP: Open Closed Principle (Принцип
открытости-закрытости)
Цель:
• Программные элементы (классы, модули,
функции) должны быть открыты для
расширения, но закрыты для модификации.
21. Основные принципы разработки ПО
Пример:
class Logger
{
public function log($logText)
{
// save log into file
}
}
class SmtpMailer
{
private $logger;
public function __construct()
{
$this->logger = new Logger();
}
public function sendMessage($message)
{
//... sending message
$this->logger->log(sprintf("Message sent: '%s'", $message));
}
}
22. Основные принципы разработки ПО
Проблема: Что, если мы захотим поменять
файловый логгер на логгер в базу данных?
Мы изменим SmtpMailer класс:
public function __construct()
{
//$this->logger = new Logger();
$this->logger = new DatabaseLogger();
}
Нарушение принципа!
23. Основные принципы разработки ПО
Решение:
interface ILogger
{
public function log($logText);
}
class Logger implements ILogger
{
public function log($logText)
{
// save log into file
}
}
class SmtpMailer
{
private $logger;
public function __construct(ILogger $logger)
{
$this->logger = $logger;
}
public function sendMessage($message)
{
//... sending message
$this->logger->log(sprintf("Message sent: '%s'", $message));
}
}
24. Основные принципы разработки ПО
LSP: Liskov Substitution Principle (Принцип
подстановки Лисков)
Цель:
• Функции, которые используют объекты
определённых классов, не должны
ломаться, если в них передать наследники
от этих классов.
26. Основные принципы разработки ПО
Для того, чтоб не нарушался этот принцип,
класс должен иметь Контракт и наследники
не должны его нарушать.
Контракт – список публичных методов с
ожидаемым поведением.
27. Основные принципы разработки ПО
ISP: Interface Segregation Principle (Принцип
сегрегации интерфейса)
Цель:
• Не следует заставлять клиентов зависеть от
интерфейсов, которые им не нужны.
29. Основные принципы разработки ПО
Пример:
abstract class ServiceClient
{
public function getServiceUrl() {...}
public function setServiceUrl($url) {...}
abstract public function sendData($data);
abstract public function flush();
}
class HttpServiceClient extends ServiceClient
{
public function sendData($data)
{
$channel = Channel::openChannel($this->getServiceUrl());
$channel->send($data);
}
public function flush()
{
//do nothing!
}
}
class BufferingHttpServiceClient extends ServiceClient
{
public function sendData($data)
{
$this->buffer .= $data;
}
public function flush()
{
$channel = Channel::openChannel($this->getServiceUrl());
$channel->send($this->buffer);
}
}
30. Основные принципы разработки ПО
Решение:
abstract class ServiceClient
{
public function getServiceUrl() {...}
public function setServiceUrl($url) {...}
abstract public function sendData($data);
}
abstract class BufferingServiceClient extends ServiceClient
{
abstract public function flush();
}
class HttpServiceClient extends ServiceClient
{
public function sendData($data)
{
$channel = Channel::openChannel($this->getServiceUrl());
$channel->send($data);
}
}
class BufferingHttpServiceClient extends BufferingServiceClient
{
public function sendData($data)
{
$this->buffer .= $data;
}
public function flush()
{
$channel = Channel::openChannel($this->getServiceUrl());
$channel->send($this->buffer);
}
}
31. Основные принципы разработки ПО
DIP: Dependency Inversion Principle (Принцип
инверсии зависимости)
Цели:
• Высокоуровневые модули или классы не
должны зависить от низкоуровневых, оба
должны зависеть от абстракций.
• Абстракции не должны зависить от деталей
реализации. Детали реализации должны
зависеть от абстракций.
35. Основные принципы разработки ПО
Код:
class KeyboardReader
{
public function read() {...}
}
class FileWriter
{
public function write($data, $filename) {...}
}
class Copier
{
protected $_reader;
protected $_writer;
public function __construct()
{
$this->_reader = new KeyboardReader();
$this->_writer = new FileWriter();
}
public function copy($toFile)
{
$data = $this->_reader->read();
$this->_writer->write($data, $toFile);
}
}
36. Основные принципы разработки ПО
Проблемы:
• Класс Copier не тестируемый, так как нельзя
изолироваться от KeyboardReader и
FileWriter.
• Теперь в любом месте приложения ты
должен использовать Copier только с
классами KeyboardReader и FileWriter, даже
если захочешь использовать PrinterWriter
класс.
38. Основные принципы разработки ПО
Решение:
interface IReader
{
public function read();
}
interface IWriter
{
public function write($data);
}
class KeyboardReader implements IReader
{
public function read() {...}
}
class FileWriter implements IWriter
{
public function __construct($filename)
{
$this->_filename = $filename;
}
public function write($data) {...}
}
class Copier
{
protected $_reader;
protected $_writer;
public function __construct(IReader $reader, IWriter $writer)
{
$this->_reader = $reader;
$this->_writer = $writer;
}
public function copy()
{
$data = $this->_reader->read();
$this->_writer->write($data);
}
}
39. Основные принципы разработки ПО
Что мы получили:
• Лёгкое тестирование. Reader и Writer можем
подменить Mock-объектами.
• Гибкость. Теперь в любом месте приложения
мы можем использовать Reader и Writer, какой
захотим.
• Намного более прозрачный контракт. В
процессе разработки нам не нужно
заглядывать в реализацию Reader и Writer,
достаточно посмотреть интерфейсы, чтобы
понять, что они делают.