Учебный Центр Luxoft www.luxoft.ru/edu
Разработка через тестирование
(Test Driven Development)
Development)
EKrivosheev@luxoft.com
О вашем инструкторе
Имя
Статусы
Контакты
1-2
Рабочие материалы
Презентация
Рабочая среда на ПК
1-3
1
Учебный Центр Luxoft www.luxoft.ru/edu
Цели курса...
курса...
По окончании данного курса
слушатели:
получат общее понятие о целях и
задачах тестирования
освоят технологии модульного
тестирования
научатся применять шаблоны при
разработке модульных тестов
1-4
Цели курса…
курса…
По окончании данного курса
слушатели:
ознакомятся с синтаксисом и
приобретут опыт практического
использования одной из сред
тестирования семейства xUnit
(JUnit/NUnit)
освоят практику разработки через
тестирование
1-5
...Цели курса
...Цели
По окончании данного курса
слушатели:
научаться применять шаблоны TDD
получат практический опыт
разработки приложения с
использованием TDD
1-6
2
Учебный Центр Luxoft www.luxoft.ru/edu
Необходимая подготовка
Слушатели должны:
иметь опыт разработки на одном из
языков программирования: Java/C#
1-7
Знакомство
Напишите свое имя на пирамидке,
пожалуйста
Ваш опыт разработки на Java/С#
Ваш опыт работы с JUnit
Ваш опыт разработки по TDD
1-8
Расписание
День 1 Модуль 0: Введение
Модуль 1: Тестирование в разработке
ПО
Модуль 2: Библиотека модульного
тестирования JUnit
День 2 Модуль 3: Test-driven development
Модуль 4: TDD Workshop
1-9
3
Учебный Центр Luxoft www.luxoft.ru/edu
Общие рекомендации
Пожалуйста,
Отключите телефоны
Задавая вопросы, старайтесь
придерживаться темы обсуждения
Если у вас возникли вопросы, не
относящиеся к теме, пометьте их, и
инструктор будет рад ответить при
подходящем случае
1-10
Организация обучения
Время начала и конца занятий
Перерывы
Питание
1-11
Вопросы и ответы
1-12
4
Учебный Центр Luxoft www.luxoft.ru/edu
План курса
Модуль 1: Модульное тестирование
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-13
Тестирование – способ
обеспечения качества продукта
Качество ПО
Качество программного продукта
характеризуется набором свойств,
определяющих, насколько продукт "хорош"
с точки зрения заинтересованных сторон
2-14
Тестирование – способ
обеспечения качества продукта
Качество ПО
Заинтересованными сторонами являются:
заказчик продукта
спонсор
конечный пользователь
разработчики
тестировщики продукта
инженеры поддержки
сотрудники отделов маркетинга, обучения и
продаж
2-15
5
Учебный Центр Luxoft www.luxoft.ru/edu
Тестирование – способ
обеспечения качества продукта
Качество ПО
Каждый из участников может иметь
различное представление о продукте и о
том, насколько он хорош или плох (то есть
о том, насколько высоко качество
продукта)
2-16
Тестирование – способ
обеспечения качества продукта
Качество ПО
Таким образом, постановка задачи
обеспечения качества продукта
выливается в задачи:
определения заинтересованных лиц
их критериев качества
нахождения оптимального решения,
удовлетворяющего этим критериям
2-17
Тестирование – способ
обеспечения качества продукта
Тестирование: общие понятия
Тестирование является одним из
наиболее устоявшихся способов
обеспечения качества разработки
программного обеспечения
Оно является одним из эффективных
средств современной системы обеспечения
качества программного продукта
Верификация и валидация ПО
2-18
6
Учебный Центр Luxoft www.luxoft.ru/edu
Тестирование – способ
обеспечения качества продукта
Тестирование: общие понятия
С технической точки зрения,
тестирование заключается в:
выполнении приложения на некотором
множестве исходных данных
сверке получаемых результатов с заранее
известными (эталонными) с целью
установить соответствие различных
свойств и характеристик приложения
заказанным свойствам
2-19
Тестирование – способ
обеспечения качества продукта
Тестирование: общие понятия
Тестирование является одной из
основных фаз разработки программного
продукта (наряду с Дизайном приложения
и Разработкой кода)
Оно характеризуется достаточно большим
вкладом в суммарную трудоемкость
разработки продукта
2-20
Тестирование – способ
обеспечения качества продукта
Эффективность автоматизации
Широко известна оценка распределения
трудоемкости между фазами создания
программного продукта: 40%-20%-40%*
(см. рисунок)
Оценка распределения трудоемкости и
стоимости исправления ошибки
*Котляров В.П., Основы тестирования программного обеспечения
2-21
7
Учебный Центр Luxoft www.luxoft.ru/edu
Тестирование – способ
обеспечения качества продукта
Эффективность автоматизации
Следовательно, наибольший эффект в
снижении трудоемкости может быть
получен прежде всего на фазах Design и
Testing
А значит и основные вложения в
автоматизацию или генерацию кода
следует осуществлять, прежде всего, на
этих фазах
2-22
Тестирование – способ
обеспечения качества продукта
Эффективность автоматизации
Как видно из графика, стоимость
исправления ошибок минимальна на
стадиях дизайна и разработки
Т.е. было бы неплохо обнаруживать
большую часть ошибок до начала фазы
тестирования
2-23
Тестирование – способ
обеспечения качества продукта
Эффективность автоматизации
Однако технологии автоматизированного
тестирования дизайна (верификации
требований и спецификаций) только
начинают появляться
Трейсинг (дизайна и требований)
В то же время, автоматизированное
тестирование кода является широко
распространенной практикой
2-24
8
Учебный Центр Luxoft www.luxoft.ru/edu
Тестирование – способ
обеспечения качества продукта
Эффективность автоматизации
Таким образом, процесс тестирования
затрагивает все фазы производства ПО,
однако не на всех фазах он может быть
успешно автоматизирован
2-25
Тестирование – способ
обеспечения качества продукта
Уровни тестирования
Разработка системы, как правило, идет на
различных уровнях:
вначале разрабатывается концепция системы,
системные требования
затем архитектура системы, ее разбиение на
модули
затем разрабатываются отдельные модули
Процесс верификации также разбивается на
отдельные уровни
2-26
План курса
Модуль 1: Модульные тесты
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-27
9
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни тестирования
Уровни тестирования
системное тестирование, в ходе
которого тестируется система в целом;
интеграционное тестирование, в ходе
которого тестируются группы
взаимодействующих модулей и компонент
системы;
модульное тестирование, в ходе
которого тестируются отдельные
компоненты
2-28
Уровни тестирования
Системное тестирование
Системное тестирование
(System Testing):
Основной задачей системного
тестирования является проверка как
функциональных, так и нефункциональных
требований в системе в целом
2-29
Уровни тестирования
Системное тестирование
В ходе системного тестирования
выявляются следующие дефекты:
неверное использование ресурсов системы
непредусмотренные комбинации данных
пользовательского уровня
несовместимость с окружением
непредусмотренные сценарии использования
отсутствующая или неверная
функциональность
неудобство использования и т.д.
2-30
10
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни тестирования
Системное тестирование
Для минимизации рисков, связанных с
особенностями поведения в системы в той
или иной среде, во время тестирования
рекомендуется использовать окружение
максимально приближенное к тому, на
которое будет установлен продукт после
выдачи
2-31
Уровни тестирования
Интеграционное тестирование
Интеграционное тестирование
(Integration Testing):
Интеграционное тестирование
предназначено для проверки связи между
компонентами, а также взаимодействия с
различными частями системы
(операционной системой, оборудованием
либо связи между различными системами)
2-32
Уровни тестирования
Интеграционное тестирование
Интеграционное тестирование так же
может проводиться на различных уровнях:
Компонентный: проверяется
взаимодействие между компонентами
системы после проведения компонентного
(модульного) тестирования
Системный: проверяется взаимодействие
между разными системами после
проведения системного тестирования
2-33
11
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни тестирования
Модульное тестирование
Компонентное или Модульное
тестирование
(Component or Unit Testing):
Модульное тестирование проверяет
функциональность и ищет дефекты в
частях приложения, которые доступны и
могут быть протестированы по
отдельности (модули программ, объекты,
классы, функции и т.д.)
2-34
Уровни тестирования
Модульное тестирование
Обычно компонентное (модульное)
тестирование проводится вызывая код,
который необходимо проверить (при
поддержке среды разработки)
Все найденные дефекты, как правило,
исправляются в коде без формального их
описания в системе управления ошибками
2-35
План курса
Модуль 1: Модульные тесты
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного
тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-36
12
Учебный Центр Luxoft www.luxoft.ru/edu
Цели и задачи модульного
тестирования
Модульное тестирование
Каждая сложная программная система
состоит из отдельных частей - модулей,
выполняющих ту или иную функцию в
составе системы
Для того, чтобы удостовериться в
корректной работе всей системы,
необходимо вначале протестировать
каждый модуль системы по отдельности
2-37
Цели и задачи модульного
тестирования
Модульное тестирование
В случае возникновения проблем при
тестировании системы в целом это
позволяет проще выявить модули,
вызвавшие проблему, и устранить
соответствующие дефекты в них
Такое тестирование модулей по
отдельности получило называние
модульного тестирования
(unit testing) 2-38
Цели и задачи модульного
тестирования
Определения
Тестовый драйвер (среда, фреймворк)
– система, позволяющая выполнять и
контролировать результат выполнения
тестов. Как правило, позволяет также
корректировать входные данные, включает
в себя систему обработки исключительных
ситуаций и восстановления, средства
параметризации тестов и управления
данными
2-39
13
Учебный Центр Luxoft www.luxoft.ru/edu
Цели и задачи модульного
тестирования
Определения
Заглушка – объект, предназначенный для
симуляции поведения реального объекта
во время тестирования
Тест-план – представляет собой документ,
в котором перечислены либо все тестовые
примеры, необходимые для тестирования
системы, либо часть тестовых примеров,
объединенных по определенному признаку
2-40
Цели и задачи модульного
тестирования
Определения
Тест-требования – содержат описание
требований по проверке всех основных
функций системы
Для каждого модуля, подвергаемого
тестированию, разрабатывается тестовое
окружение, включающее в себя драйвер и
заглушки, готовятся тест-требования и
тест-планы, описывающие конкретные
тестовые примеры 2-41
Цели и задачи модульного
тестирования
Цели модульного тестирования
Основная цель модульного тестирования -
удостовериться в соответствии
требованиям каждого отдельного модуля
системы перед тем, как будет произведена
его интеграция в состав системы
2-42
14
Учебный Центр Luxoft www.luxoft.ru/edu
Цели и задачи модульного
тестирования
Задачи модульного тестирования
В ходе модульного тестирования решаются
следующие основные задачи:
Поиск и документирование несоответствий
требованиям
Поддержка разработки и рефакторинга
низкоуровневой архитектуры системы и
межмодульного взаимодействия
Поддержка рефакторинга модулей
Поддержка устранения дефектов и отладки2-43
Цели и задачи модульного
тестирования
Задачи модульного тестирования
Рефакторинг – процесс полного или
частичного преобразования внутренней
структуры программы при сохранении её
внешнего поведения. В его основе лежит
последовательность небольших
эквивалентных (т.е., сохраняющих
поведение) преобразований.
2-44
Цели и задачи модульного
тестирования
Задачи модульного тестирования
Поиск и документирование
несоответствий требованиям -
классическая задача тестирования,
включающая в себя не только разработку
тестового окружения и тестовых примеров,
но и выполнение тестов,
протоколирование результатов
выполнения, составление отчетов о
проблемах
2-45
15
Учебный Центр Luxoft www.luxoft.ru/edu
Цели и задачи модульного
тестирования
Задачи модульного тестирования
Поддержка разработки и
рефакторинга низкоуровневой
архитектуры системы и
межмодульного взаимодействия –
модульные тесты помогают выявить
проблемы в дизайне системы и
нелогичные или запутанные механизмы
работы с модулем
2-46
Цели и задачи модульного
тестирования
Задачи модульного тестирования
Поддержка устранения дефектов и
отладки сопряжена с обратной связью,
которую получают разработчики от
тестировщиков в виде отчетов о
проблемах
2-47
Цели и задачи модульного
тестирования
Задачи модульного тестирования
Подробные отчеты о проблемах,
составленные на этапе модульного
тестирования, позволяют локализовать и
устранить многие дефекты в программной
системе на ранних стадиях ее разработки
или разработки ее новой
функциональности
2-48
16
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Проблемы определения модуля
В силу того, что модули, подвергаемые
тестированию, обычно невелики по
размеру, модульное тестирование
считается наиболее простым (хотя и
достаточно трудоемким) этапом
тестирования системы
Однако, несмотря на внешнюю простоту, с
модульным тестированием сопряжены две
проблемы:
2-49
Определение модуля
Проблемы определения модуля
не существует единых принципов
определения того, что в точности является
отдельным модулем
трактовка понятия модульного
тестирования - понимается ли под ним
обособленное тестирование модуля, работа
которого поддерживается только тестовым
окружением, или речь идет о проверке
корректности работы модуля в составе уже
разработанной системы
2-50
Определение модуля
Традиционное определение
Традиционное определение модуля с точки
зрения его тестирования:
модуль - это компонент минимального
размера, который может быть независимо
протестирован в ходе верификации
программной системы
В реальности часто возникают проблемы с
тем, что считать модулем
2-51
17
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Альтернативные определения
Существует несколько подходов к данному
вопросу:
модуль - это часть программного кода,
выполняющая одну функцию с точки
зрения функциональных требований
модуль - это программный модуль, т.е.
минимальный компилируемый элемент
программной системы
2-52
Определение модуля
Альтернативные определения
модуль - это задача в списке задач
проекта (с точки зрения его менеджера)
модуль - это участок кода, который может
уместиться на одном экране или одном
листе бумаги
модуль - это один класс или их
множество с единым интерфейсом
модуль - это одна функция или метод
2-53
Определение модуля
Границы модуля
Обычно за тестируемый модуль
принимается либо программный модуль
(единица компиляции) в случае, если
система разрабатывается на процедурном
языке, или класс, если система
разрабатывается на объектно-
ориентированном языке
2-54
18
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Границы модуля
В случае систем, написанных на
процедурных языках, процесс
тестирования модуля происходит
достаточно просто – для каждого модуля
разрабатывается:
тестовый драйвер, вызывающий функции
модуля и собирающий результаты их
работы
набор заглушек, которые имитируют
поведение функций, содержащихся в
других модулях
2-55
Определение модуля
Границы модуля
При тестировании объектно-
ориентированных систем существует ряд
особенностей, прежде всего вызванных
инкапсуляцией данных и методов в
классах (декомпозиция класса нарушит
принцип инкапсуляции, согласно которому
объекты каждого класса должны вести
себя как единое целое с точки зрения
других объектов)
2-56
Определение модуля
Границы модуля
Кроме того, более мелкое деление классов
и использование отдельных методов в
качестве тестируемых модулей
нецелесообразно, поскольку для
тестирования каждого метода потребуется
разработка тестового окружения,
сравнимого по сложности с уже
написанным программным кодом класса
2-57
19
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Компонентное тестирование
Процесс тестирования классов как модулей
иногда называют компонентным
тестированием
В ходе такого тестирования проверяется
взаимодействие методов внутри класса и
правильность доступа методов к
внутренним данным класса
2-58
Определение модуля
Компонентное тестирование
На данном этапе возможно обнаружение
не только стандартных дефектов
(связанных с выходами за границы
диапазона или неверно реализованными
требованиями), но и специфических
дефектов объектно-ориентированного
программного обеспечения
2-59
Определение модуля
Специфические дефекты ООП
Такими дефектами являются:
дефекты инкапсуляции, в результате
которых, например, сокрытые данные
класса оказываются недоступными для
соответствующих публичных методов
дефекты наследования, при наличии
которых схема наследования блокирует
важные данные или методы от классов-
потомков
2-60
20
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Специфические дефекты ООП
дефекты полиморфизма, при которых
полиморфное поведение класса
оказывается распространенным не на все
возможные классы
дефекты инстанцирования, при
которых во вновь создаваемых объектах
класса не устанавливаются корректные
значения по умолчанию параметров и
внутренних данных класса
2-61
Определение модуля
Проблемы тестирования в ООП
Однако, выбор класса в качестве
тестируемого модуля имеет и ряд
сопряженных проблем:
Определение степени полноты
тестирования класса
Протоколирование состояний объектов и
их изменений
Тестирование изменений
2-62
Определение модуля
Проблемы тестирования в ООП
Определение степени полноты тестирования
класса:
В том случае, если в качестве
тестируемого модуля выбран класс, не
совсем ясно, как определять степень
полноты его тестирования
Классический критерий полноты покрытия:
тесты можно считать полными, если
выполнены все структурные элементы всех
методов, как публичных, так и скрытых 2-63
21
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Проблемы тестирования в ООП
Однако существует альтернативный
подход к тестированию класса: все
публичные методы должны предоставлять
пользователю данного класса
согласованную схему работы
В этом случае достаточно проверить
типичные корректные и некорректные
сценарии работы с данным классом
2-64
Определение модуля
Проблемы тестирования в ООП
Протоколирование состояний объектов и их
изменений:
Некоторые методы класса предназначены
не для выдачи информации пользователю,
а для изменения внутренних данных
объекта класса
2-65
Определение модуля
Проблемы тестирования в ООП
Значение внутренних данных объекта
определяет его состояние в каждый
определенный момент времени, а вызов
методов, изменяющих данные, изменяет и
состояние объекта
При тестировании классов необходимо
проверять, что класс адекватно реагирует
на внешние вызовы в любом из состояний
2-66
22
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Проблемы тестирования в ООП
Однако, зачастую из-за инкапсуляции
данных невозможно определить
внутреннее состояние класса
программными способами внутри драйвера
Автоматизированное тестирование в этом
случае может лишь определить, по всем
ли выявленным состояниям
осуществлялись переходы и все ли
возможные реакции проверялись
2-67
Определение модуля
Проблемы тестирования в ООП
Тестирование изменений:
В результате рефакторинга только одного
класса, как правило, не меняется его
внешний интерфейс с другими классами
(интерфейсы меняются при рефакторинге
сразу нескольких классов)
2-68
Определение модуля
Проблемы тестирования в ООП
В результате обычных эволюционных
изменений системы у класса может
меняться внешний интерфейс:
по формальным признакам –изменяются
имена и состав методов, их параметры
по функциональным признакам – при
сохранении внешнего интерфейса меняется
логика работы методов (contract)
2-69
23
Учебный Центр Luxoft www.luxoft.ru/edu
Определение модуля
Проблемы тестирования в ООП
Для проведения модульного тестирования
класса после таких изменений потребуется
изменение драйвера и, возможно,
заглушек
Но только модульного тестирования в
данном случае недостаточно, необходимо
также проводить и интеграционное
тестирование данного класса вместе со
всеми классами, которые связаны с ним по
данным или по управлению 2-70
Определение модуля
Проблемы тестирования в ООП
Связь по управлению реализуется путем
вызова одного модуля из другого.
Вызванный модуль после завершения
своей работы возвращает управление
вызвавшему его модулю.
Связь по данным реализуется двумя
способами:
использование параметров при вызове
модулей
использование общих областей данных 2-71
Отличия модульного
тестирования и отладки
Определение отладки
Отладка — этап разработки
компьютерной программы, на котором
обнаруживают, локализуют и устраняют
ошибки
Чтобы понять, где возникла ошибка,
приходится:
узнавать текущие значения переменных
выяснять, по какому пути выполнялась
программа 2-72
24
Учебный Центр Luxoft www.luxoft.ru/edu
Отличия модульного
тестирования и отладки
Технологии отладки
Существуют две взаимодополняющие
технологии отладки:
Использование отладчиков — программ,
которые включают в себя
пользовательский интерфейс для
пошагового выполнения программы, с
остановками на указанных строках
исходного кода или при выполнении
определённого условия
2-73
Отличия модульного
тестирования и отладки
Технологии отладки
Вывод текущего состояния программы с
помощью расположенных в критических
точках программы операторов вывода —
на экран, принтер, или в файл (вывод
отладочных сведений в файл называется
журналированием)
2-74
Отличия модульного
тестирования и отладки
Проблемы при отладке
Однако использование отладки сопряжено с
рядом проблем:
Отладка метода, глубоко «закопанного» в
большом приложении, или
воспроизведение тестовой ситуации
зачастую становятся чрезвычайно
трудоемкими
2-75
25
Учебный Центр Luxoft www.luxoft.ru/edu
Отличия модульного
тестирования и отладки
Проблемы при отладке
Иногда не удается проверить только что
написанный код, потому что он еще нигде
и никак не используется
В некоторых ситуациях программисты
даже разрабатывают специальные
утилиты, позволяющие отладить тот или
иной компонент отдельно от всей системы
2-76
Отличия модульного
тестирования и отладки
Преимущества модульных тестов
При использовании модульных тестов
подобных проблем просто не возникает:
Если нужно что-нибудь проверить –
пишется соответствующий тест
Тесты представляют собой практически
идеальную отладочную среду: они
находятся полностью под контролем
программиста и позволяют вызвать любой
код в широком диапазоне условий 2-77
Отличия модульного
тестирования и отладки
Преимущества модульных тестов
Для большинства ошибок, найденных при
модульном тестировании, отладка вообще
не требуется, поскольку точно известны:
место их возникновения (код, который был
написан только что)
условия воспроизведения (тест, который
сейчас отлаживается)
2-78
26
Учебный Центр Luxoft www.luxoft.ru/edu
Организация модульного
тестирования
Фазы тестирования
Формально, процесс тестирования можно
разделить на следующие фазы:
планирование
разработка набора тестов
выполнение тестов и сбор статистики,
каждая из которых характеризуется
определенным набором активностей
2-79
Организация модульного
тестирования
Фаза планирования
На этапе планирования формируются общие
принципы тестирования в проекте:
степень полноты и охвата тестирования
источники входных и выходных данных
технологии проверки результатов и формат
их записи
требования к завершению тестирования
2-80
Организация модульного
тестирования
Фаза планирования
А так же анализируются свойства каждого из
модулей:
функциональные требования
дополнительные требования (напр.
системные)
характеристики входных и выходных
данных
определение состояний модуля (если
модуль можно представить в виде
конечного автомата) 2-81
27
Учебный Центр Luxoft www.luxoft.ru/edu
Организация модульного
тестирования
Фаза планирования
После анализа требований к модулям,
возможно возникнет необходимость внести
корректировки в общие принципы
тестирования
После этого фазу планирования можно
считать оконченной
2-82
Организация модульного
тестирования
Фаза разработки тестов
В ходе этапа разработки тестов должны
быть решены следующие задачи:
разработка архитектуры тестовых наборов
разработка тестовых сценариев (test-
case) и тестовых наборов (test-suite)
разработка нефункциональных тестов,
(напр., основанных на архитектуре)
составление спецификаций тестов
(документирование) 2-83
Организация модульного
тестирования
Фаза разработки тестов
тестовый сценарий – определение
набора входных данных теста, условий
выполнения и ожидаемых результатов,
указанных с целью оценки некоторого
аспекта тестируемого элемента
тестовый набор – набор тестовых
сценариев, объединенных по какому-либо
признаку (напр., тестирующих конкретный
модуль или его часть его
функциональности)
2-84
28
Учебный Центр Luxoft www.luxoft.ru/edu
Организация модульного
тестирования
Фаза разработки тестов
В ходе разработки тестовых сценариев так
же выполняются следующие задачи:
формируются тестовые наборы данных
создается тестовое окружение
осуществляется интеграция тестового
окружения с тестируемым модулем
2-85
Организация модульного
тестирования
Фаза выполнения и анализа
После того, как все тесты реализованы,
они выполняются в ручном или
автоматическом режиме
Вне зависимости от вида тестирования в
ходе этого этапа решаются две задачи:
выполнение тестовых примеров
сбор и анализ результатов тестирования
2-86
Организация модульного
тестирования
Фаза выполнения и анализа
Сбору подлежит следующая информация:
результат выполнения каждого тестового
сценария (прошел/не прошел)
информация об информационном
окружении системы в случае, если тест не
прошел
информация о ресурсах, которые
потребовались для выполнения тестового
примера
2-87
29
Учебный Центр Luxoft www.luxoft.ru/edu
Организация модульного
тестирования
Фаза выполнения и анализа
По результатам анализа этой информации
производится изменение требований,
программного кода, тестов или тестового
окружения
Этапы разработки (доработки), реализации
и выполнения тестов продолжаются до тех
пор, пока не будет достигнут критерий
завершения модульного тестирования
(напр., 90% покрытие тестами исходного
кода)
2-88
Использование mock- и
mock-
stub-объектов
stub-
Общие понятия
Классы (модули), как правило, редко
бывают полностью изолированными, и
используют в своей работе другие классы
Например, слой бизнес логики (Business
Logic Layer) часто работает с другими
объектами бизнес логики или обращается
к слою доступа к данным (Data Access
Layer)
2-89
Использование mock- и
mock-
stub-объектов
stub-
Общие понятия
В таких случаях на помощь приходят
mock-объекты (заглушки),
предназначенные для симуляции
поведения реальных объектов во время
тестирования
Понятие mock-объект может обозначать
как любой из видов заглушек (Test
Doublers, тестовых дублеров), так и
конкретный их вид – mock-объекты
2-90
30
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub-
Общие понятия
Все тест-дублеры делятся на 4 группы*:
dummy-объекты
fake-объекты
stub-объекты
mock-объекты
*Gerard Meszaros – “XUnit Test Patterns”, Martin Fawler – “Refactoring: Improving
the Design of Existing Code”
2-91
Использование mock- и
mock-
stub-объектов
stub-
Dummy-объекты
Dummy – пустые объекты, которые
передаются в вызываемые внутренние
методы, но не используются
(предназначены лишь для заполнения
параметров методов)
2-92
Использование mock- и
mock-
stub-объектов
stub-
Dummy-объекты
public void testInvoice_addLineItem() {
final int QUANTITY = 1;
Product product = new Product(getUniqueNumberAsString(),
getUniqueNumber());
City city = new City(“Vladivostok", “Russia”);
Address address = new Address(“Lenin St, 12", city, “650243");
Customer customer= new Customer(getUniqueNumberAsString(),
getUniqueNumberAsString(), address);
Invoice inv = new Invoice(customer);
// Вызов
inv.addItemQuantity(product, QUANTITY);
// Проверка
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
2-93
31
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub-
Dummy-объекты
public void testInvoice_addLineItem() {
final int QUANTITY = 1;
Product product = new Product(“Dummy product name”,
getUniqueNumber());
Invoice inv = new Invoice(new DummyCustomer);
// Вызов
inv.addItemQuantity(product, QUANTITY);
// Проверка
List lineItems = inv.getLineItems();
assertEquals("number of items", lineItems.size(), 1);
LineItem actual = (LineItem)lineItems.get(0);
LineItem expItem = new LineItem(product, QUANTITY);
assertLineItemsEqual("",expItem, actual);
}
2-94
Использование mock- и
mock-
stub-объектов
stub-
Dummy-объекты
public class DummyCustomer implements ICustomer {
public DummyCustomer() {
// Конструктор оставляем пустым – никакой инициализации
не требуется
}
public int getTimeZone() {
throw new RuntimeException(“Этот метод не должен быть
вызван!”);
}
}
2-95
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Stub:
объекты, которые предоставляют заранее
заготовленные ответы на вызовы во время
выполнения теста и обычно не отвечающие ни
на какие другие вызовы, которые не
требуются в тесте
также могут запоминать какую-то
дополнительную информацию о количестве
вызовов, параметрах и возвращать их потом
тесту для проверки
2-96
32
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Выделяют несколько видов stub’ов, в
зависимости от целей применения:
Responder (ответчик)
используется для эмуляции корректного
поведения объекта
как правило, используется в happy-path-
тестах
используется, когда реальный объект еще
не реализован, либо недоступен в
девелоперском окружении 2-97
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Saboteur (диверсант)
используется для эмуляции некорректного
поведения объекта
призван всеми возможными способами
вызвать «крах» системы, выдавая
некорректные значения, исключения и т.п.
вне зависимости от получаемых входных
данных
используется для тестирования обработки
различных отказов
2-98
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Temporary stub (временная заглушка)
используется для замены еще не
реализованного объекта
как правило, возвращает hardcoded-
значения
заменяется реальным объектом, как только
это становится возможным, либо сам
постепенно становится этим объектам,
постепенно получая функциональное
наполнение
2-99
33
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Entity chain snipping
используется для замены сложной системы
взаимодействующих объектов одним
упрощает процедуру инициализации
тестового окружения
делает тесты более понятными
2-100
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
В зависимости от реализации, stub’ы
бывают:
Hard-coded stub (статические)
на любые запросы выдают одно и то же
жестко прописанное значение
как правило, пишутся для конкретного
теста, либо их небольшого набора
2-101
Использование mock- и
mock-
stub-объектов
stub -
Stub-объекты
Configurable stub (конфигурируемые)
используются для того, чтобы для каждого
теста не писать свой статический stub
тест конфигурирует stub во время своей
инициализации
2-102
34
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Fake-объекты
К сожалению, редко можно обойтись
простыми dummy- и stub-объектами
Иногда, тестируемый метод может
обращаться к базе данных, web-сервисам,
или файловой системе
В этом случае, наш объект должен уметь
«симулировать» требуемые действия,
выполняя более простой код (напр.,
сохранять данные в памяти вместо БД) 2-103
Использование mock- и
mock-
stub-объектов
stub -
Fake-объекты
Fake – объекты, имеющие работающие
реализации, но в таком виде, который
делает их неподходящими для production-
кода
Примеры fake-объектов:
Fake database – реальная БД заменяется
аналогичным по функциональности, но
более «легким» аналогом (который даст
выигрыш при тестировании, но не
выдержит production нагрузки)
2-104
Использование mock- и
mock-
stub-объектов
stub -
Fake-объекты
In-memory database – реальная БД
заменяется набором HashTable’s, либо её
небольшим «слепком», размещаемым в
памяти на время выполнения
Fake web-service – реальный web-сервис
заменяется локальной реализацией,
возвращающей определенное значение
(или набор значений), что позволяет
выполнять тесты вне зависимости от
доступности сервиса 2-105
35
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Mock-объекты
Mock:
объекты, которые заменяют реальный
объект в условиях теста и позволяют
проверять вызовы своих членов как часть
системы или модульного теста
содержат заранее запрограммированные
ожидания вызовов, которые они должны
получить
применяются в основном для interaction
testing
2-106
Использование mock- и
mock-
stub-объектов
stub -
Mock-объекты
Во время инициализации теста, мы
создаем и настраиваем mock-объект
(определяем возвращаемые значения,
ожидаемые вызовы и их аргументы)
Во время выполнения, mock-объект
сравнивает получаемые вызовы и
значения с ожидаемыми, и «заваливает»
тест при несовпадении
По окончании теста проверяется наличие
методов, которые не были вызваны 2-107
Использование mock- и
mock-
stub-объектов
stub -
Mock-объекты
Рассмотрим пример: при удалении
пользователя, сообщение об этом должно
появляться в логе
public void TestRemoveUser() {
CommonUser expectedCU = createCommonUser();
UserManagementFacade facade =
new UserManagementFacadeImpl();
facade.removeUser(expectedCU.getUserNumber());
assert.False(“User should not exist after removing”,
facade.userExists(expectedCU.getUserNumber()));
}
2-108
36
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Mock-объекты
public void TestRemoveUser_Mock() {
CommonUser expectedCU = createCommonUser();
MockAuditLog mockLog = new MockauditLog();
mockLog.setExpectedLogMessage(helper.getDate(),
helper.TEST_USER_NAME,
helper.USER_REMOVE_ACTION_CODE,
expectedCU.getUserNumber);
mockLog.setExpectedCallsNumber(1);
UserManagementFacade facade = new
UserManagementFacadeImpl();
facade.setAuditLog(mockLog);
facade.removeUser(expectedCU.getUserNumber());
assert.False(“User should not exist after removing”,
facade.userExists(expectedCU.getUserNumber()));
mockLog.verify();
} 2-109
Использование mock- и
mock-
stub-объектов
stub -
Mock-объекты
Реализация метода LogMessage
mock-объекта
public void LogMessage(Data actualDate, String actualUser,
String actualActionCode,
int actualUserNumber) {
actualCallsNumber++;
Assert.assertEquals(“date”, expectedDate, actualDate);
Assert.assertEquals(“user”, expectedUser, actualUser);
Assert.assertEquals(“action code”, expectedActionCode,
actualActionCode);
Assert.assertEquals(“number”, expectedNumber,
actualNumber);
} 2-110
Использование mock- и
mock-
stub-объектов
stub -
Подходы к модульному тестированию
State-based testing – подход, при
котором проверяется состояние объекта
после прохождение unit-теста
Interaction testing – подход к
модульному тестированию, при котором
тестируется взаимодействие объектов,
поведение методов, последовательность
их вызовов и т.п.
2-111
37
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Подходы к модульному тестированию
В state-based testing нас интересует, в
какое состояние перешел объект после
вызова тестируемого метода, или что
вернул наш метод и правилен ли этот
результат
Подобные проверки проводятся при
помощи вызова методов класса Assert
различных unit-тест фреймворков:
Assert.AreEqual(),
Assert.IsNull() и т.д.
2-112
Использование mock- и
mock-
stub-объектов
stub -
Подходы к модульному тестированию
В interaction testing нас интересует прежде
всего не статическое состояние объекта, а
те динамические вызовы методов, которые
происходят у него внутри
В этом случае используют специальные
mock-фреймворки, содержащие
определенные конструкции для записи
ожиданий и их последующей проверки
(методы Verify(), VerifyAll() и т.п.)
2-113
Использование mock- и
mock-
stub-объектов
stub -
Подходы к модульному тестированию
Martin Fawler называет эти два подхода
классическим (classical) и мокистским
(mockist) unit-тестированием и делит
программистов на предпочитающих
первый и второй подходы
На самом деле, иногда просто удобнее
проверить состояние объекта, а иногда –
его взаимодействие с другими объектами
2-114
38
Учебный Центр Luxoft www.luxoft.ru/edu
Использование mock- и
mock-
stub-объектов
stub -
Подходы к модульному тестированию
Эти два подхода прекрасно уживаются
вместе, когда вы понимаете, о чем идет
речь, и что именно вы хотите сейчас
проверить
Точно так же, как уживаются в одном
тесте mock’и и stub’ы
2-115
План курса
Модуль 1: Модульные тесты
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-116
Понятие покрытия
программного кода
Понятие полноты системы тестов
Одной из оценок качества системы тестов
является полнота – величина той части
функциональности системы, которая
проверяется тестами
Полная система позволяет утверждать,
что система реализует всю
функциональность, указанную в
требованиях
2-117
39
Учебный Центр Luxoft www.luxoft.ru/edu
Понятие покрытия
программного кода
Понятие полноты системы тестов
Кроме того, это позволяет утверждать, что
система не реализует никакой другой
функциональности
Степень покрытия программного кода
тестами – важный количественный
показатель, позволяющий оценить
качество как системы тестов, так и
тестируемой системы
2-118
Понятие покрытия
программного кода
Понятие полноты системы тестов
Одним из наиболее часто используемых
методов определения полноты системы
тестов является определение отношения
количества тест-требований, для которых
существуют тесты, к общему количеству
тест-требований
В данном случае речь идет о покрытии
тестами тест-требований
2-119
Понятие покрытия
программного кода
Понятие полноты системы тестов
В качестве единицы измерения степени
покрытия здесь выступает процент тест-
требований, для которых существуют
тесты
Покрытие требований позволяет оценить
степень полноты системы тестов по
отношению к функциональности системы,
но не позволяет оценить полноту по
отношению к ее программной реализации
2-120
40
Учебный Центр Luxoft www.luxoft.ru/edu
Понятие покрытия
программного кода
Понятие покрытия кода
Одна и та же функция может быть
реализована при помощи совершенно
различных алгоритмов, требующих
разного подхода к организации
тестирования
Для более детальной оценки полноты
системы тестов анализируется покрытие
программного кода, называемое также
структурным покрытием
2-121
Понятие покрытия
программного кода
Понятие покрытия кода
Во время работы каждого тестового
примера выполняется некоторый участок
программного кода системы
При выполнении всей системы тестов
выполняются все участки программного
кода, которые задействует эта система
тестов
2-122
Уровни покрытия
Уровни покрытия кода
Существует несколько различных способов
измерения покрытия, основные из них:
покрытие операторов
покрытие условий
покрытие путей
покрытие функций
покрытие вход/выход
2-123
41
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие операторов
Для обеспечения полного покрытия
программного кода на уровне операторов
необходимо, чтобы в результате
выполнения тестов каждый оператор был
выполнен хотя бы один раз
Перед началом тестирования необходимо
выделить переменные, от которых зависит
выполнение различных ветвей условий и
циклов в коде – управляющие входные
переменные 2-124
Уровни покрытия
Покрытие операторов
if (i == 0 || i == 101) {
if (showMessage) {
MessageBox.Show(“Входной параметр имеет
недопустимое значение ” + i.ToString());
} else {
System.Out.Writeln(“Входной параметр имеет
недопустимое значение ” + i.ToString());
}
return -1;
}
2-125
Уровни покрытия
Покрытие операторов
Для полного покрытия по операторам,
достаточно двух тестов:
i = 0, showMessage = true
i = 0, showMessage = false
Легко заметить, что при этом, тесты не
покрывают всей функциональности (не
протестировано поведение системы при
i = 101)
2-126
42
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие операторов
Также проблемы этого метода покрытия
можно увидеть и на примерах других
управляющих структур
Например, при проверке циклов do …
while – при данном уровне покрытия
достаточно выполнение цикла только один
раз, при этом метод совершенно
нечувствителен к логическим операторам
|| и &&
2-127
Уровни покрытия
Покрытие операторов
Другой особенностью данного метода
является зависимость уровня покрытия от
структуры программного кода
Рассмотрим простейший пример:
if (condition)
MethodA();
else
MethodB();
2-128
Уровни покрытия
Покрытие операторов
Если MethodA() содержит 99 операторов,
а MethodB() — один оператор, то
единственного теста, устанавливающего
condition в true, будет достаточно для
достижения 99%-го уровня покрытия
При этом аналогичный тестовый пример,
устанавливающий значение condition в
false, даст слишком низкий уровень
покрытия (1%)
2-129
43
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие условий
Для обеспечения полного покрытия
условий необходимо:
каждая точка входа и выхода в программе
и во всех ее функциях должна быть
выполнена по крайней мере один раз
все логические выражения в программе
должны принять каждое из возможных
значений хотя бы один раз
Таким образом, для покрытия по веткам
требуется как минимум два теста
2-130
Уровни покрытия
Покрытие условий
if (i == 0 || i == 101) {
if (showMessage) {
MessageBox.Show(“Входной параметр имеет
недопустимое значение ” + i.ToString());
} else {
System.Out.Writeln(“Входной параметр имеет
недопустимое значение ” + i.ToString());
}
return -1;
}
2-131
Уровни покрытия
Покрытие условий
Для покрытия предыдущего примера кода
по ветвям потребуется уже три теста
Это связано с тем, что первый условный
оператор if имеет неявную ветвь –
пустую ветвь else
Для обеспечения покрытия по ветвям
необходимо покрывать и пустые ветви
2-132
44
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие условий
Особенность данного уровня покрытия
заключается в том, что на нем могут не
учитываться логические выражения,
значения которых получаются вызовом
методов
Рассмотрим пример кода:
if (condition1 && (condition2 || Method()))
statement1;
else
statement2;
2-133
Уровни покрытия
Покрытие условий
Полное покрытие условий может быть
достигнуто при помощи двух тестов:
condition1 = true, condition2 = true
condition1 = false, condition2 = true/false
В обоих случаях не происходит вызова
метода Method() (хотя покрытие будет
полным)
Для его проверки необходимо добавить
еще один тест:
condition1 = true, condition2 = false
2-134
Уровни покрытия
Покрытие путей
В данном случае считаются все пути,
которые выполняются в процессе работы
тестируемого метода
Путь - уникальная последовательность
выполнения операторов, с учетом
условных операторов
Метод, содержащий в себе N условий,
имеет 2^N путей
Метод, содержащий цикл, может иметь
бесконечное число путей
2-135
45
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие путей
Т.о. в большинстве случаев 100%-е
покрытие путей обеспечить невозможно
Для решения этой проблемы, может быть
применен метод покрытия основных
(базисных, линейно-независимых)
путей
Основные пути – минимальный набор
путей, комбинация которых может
обеспечить все возможные пути
выполнения метода
2-136
Уровни покрытия
Покрытие путей
Число таких путей равно числу уникальных
условных операторов, увеличенное на 1
Рассмотрим следующий пример:
if (condition1)
statement1;
if (condition2)
statement2;
if (condition3)
statement3;
2-137
Уровни покрытия
Покрытие путей
Для достижения 100% покрытия основных
путей, нам потребуется 4 линейно-
независимых пути
Первый путь выбирается случайно (пусть
это будет путь, когда все условные
выражения принимают значение true)
Оставшиеся пути получаются поочередным
инвертированием одного из условных
выражений первого пути
2-138
46
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие путей
Таким образом, получаем четыре основных
пути, которые необходимо покрыть:
condition1 condition2 condition3
Path 1 true true true
Path 2 false true true
Path 3 true false true
Path 4 true true false
2-139
Уровни покрытия
Покрытие путей
В случае наличия циклов, может
использоваться следующий подход:
выделяем классы путей (к одному классу
можно отнести пути, отличающиеся
количеством итераций в конкретном цикле)
класс считается покрытым, если покрыт
хотя бы один путь из него
100% покрытие достигнуто, если покрыты
все классы путей
2-140
Уровни покрытия
Покрытие функций
Покрытие функций – каждая ли
функция тестируемого модуля является
выполненной хотя бы один раз
Является одним из самых простых методов
расчета покрытия, и дает довольно общее
представление о качестве тестируемого
модуля
С одной стороны, данное покрытие
говорит нам о том, что тестами покрыт
весь реализованный функционал модуля
2-141
47
Учебный Центр Luxoft www.luxoft.ru/edu
Уровни покрытия
Покрытие функций
С другой стороны, оно не гарантирует нам
адекватное поведение модуля, поскольку:
не проверяется реакция функций на все
возможные входные параметры
не проверяется реакция системы на все
возможные возвращаемые функцией
значения
2-142
Уровни покрытия
Покрытие вход/выход
Покрытие вход/выход – все ли
возможные варианты вызова функций и
возврата из них были выполнены
На данном уровне обеспечивается
тестирование как самих функций (все
возможные варианты вызова), так и их
взаимодействие в составе модуля (все
возможные варианты возврата)
2-143
Анализ покрытия
Цели и задачи анализа
К анализу покрытия программного кода
можно приступать только после полного
покрытия требований
Полное покрытие программного кода не
гарантирует того, что тесты проверяют все
требования к системе
Целью анализа полноты покрытия кода
является выявление участков кода,
которые не выполняются при выполнении
тестов 2-144
48
Учебный Центр Luxoft www.luxoft.ru/edu
Анализ покрытия
Цели и задачи анализа
В идеальном случае при полном покрытии
функциональных требований должно
получаться 100% покрытие кода
Однако на практике такое происходит
только в случае очень простого кода
Причины «недопокрытия» кода могут быть
различными
2-145
Анализ покрытия
Причины плохого покрытия кода
Недостатки в формировании тестов,
основанных на требованиях
тестовый набор должен быть дополнен
недостающими тестами
Неадекватности в требованиях
требования должны быть
модифицированы, после чего разработаны
и выполнены дополнительные тесты,
покрывающие новые требования
2-146
Анализ покрытия
Причины плохого покрытия кода
«Мертвый код»
этот код должен быть удален, и проведен
анализ для оценки эффекта удаления и
необходимости перепроверки
Дезактивируемый код – код,
работающий только в определенных
конфигурациях окружения
2-147
49
Учебный Центр Luxoft www.luxoft.ru/edu
Анализ покрытия
Причины плохого покрытия кода
Дезактивируемый код
для такого кода должна быть установлена
нормальная эксплуатационная среда, в
которой он выполняется
написаны тесты, покрывающие его
написаны тесты, проверяющие, что данный
код не может быть преднамеренно
выполнен в других конфигурациях
2-148
Анализ покрытия
Причины плохого покрытия кода
Избыточные условия
пример такого условия – выражение
!b || (a && b)
при b = false, значение переменной a не
имеет значения, т.е. условие избыточно и
вторая его часть не будет проверяться
Защитный код
2-149
Анализ покрытия
Причины плохого покрытия кода
Защитное программирование - это
метод организации программного кода
таким образом, чтобы при работе системы
последствия проявления дефектов в ней
не приводили к сбоям, отказам и авариям
(проверка входных данных, обработка
исключений и т.д.)
2-150
50
Учебный Центр Luxoft www.luxoft.ru/edu
Анализ покрытия
Причины плохого покрытия кода
Например, это может быть ветка default
в операторе выбора switch
Входное условие оператора switch может
принимать определенные значения
Как следствие, ветка default, возможно
никогда не будет выполнена
2-151
Анализ покрытия
Причины плохого покрытия кода
Защитное программирование, как правило,
не дает нам никакой информации о том,
где в системе находится дефект
Его нельзя рассматривать как замену
тестирования - эти два аспекта разработки
систем лишь дополняют друг друга
2-152
Анализ покрытия
Причины плохого покрытия кода
Также существуют случаи, когда
модульное тестирование кода сильно
затруднено, либо вообще невозможно:
генерация случайных чисел
сложные математические алгоритмы
параллельные алгоритмы
2-153
51
Учебный Центр Luxoft www.luxoft.ru/edu
Анализ покрытия
Результаты анализа
Таким образом, отсутствие покрытия
каких-либо участков кода может
являться сигналом к переработке тестов,
кода, а иногда – и требований
С другой стороны, 100% покрытие кода не
гарантирует нам, что в приложении нет
ошибок
2-154
Анализ покрытия
Результаты анализа
Помните, что тесты пишутся для
повышения качества кода и лучшего
его понимания, а не для повышения
показателей метрик
2-155
План курса
Модуль 1: Модульные тесты
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-156
52
Учебный Центр Luxoft www.luxoft.ru/edu
Наследованный код и
модульные тесты
Legacy-код
Причины появления legacy-код:
Появление новых технологий:
существуют огромные программные
комплексы, написанные на языке C++,
который считался передовым всего десять
лет назад
2-157
Наследованный код и
модульные тесты
Legacy-код
Развитие существующих технологий:
частые изменения в языках и платформах
Java/C# очень быстро приводят к
появлению унаследованных решений,
поскольку то, что когда-то считалось
новаторским, превращается в
неподдерживаемый, устаревший код
2-158
Наследованный код и
модульные тесты
Legacy-код
Бурное развитие системы: зачастую,
программисты сосредоточивают усилия
именно на реализации новых
возможностей программного обеспечения –
применяются новые технологии, но
существующий функционал при этом не
совершенствуется
2-159
53
Учебный Центр Luxoft www.luxoft.ru/edu
Наследованный код и
модульные тесты
Legacy-код
Итак, нужно ли писать тесты для legacy-
кода?
Да, нужно, но только если вы собираетесь
его изменять:
Unit-тесты позволят вам понять, как
работает код
Unit-тесты позволят вам убедиться, что
ваши изменения не нарушили логику
работы модуля
2-160
Наследованный код и
модульные тесты
Legacy-код
Ваша цель – получить тесты, которым
можно доверять
Если функция полностью покрыта
модульными тестами, то вы не будете
боятся ее изменить или даже полностью
переписать
2-161
Наследованный код и
модульные тесты
Избавление от зависимостей
Как правило, такой код имеет множество
зависимостей: подключает различные lib и
dll, посылает сообщения в сеть или другим
компонентам, отображает что-то в GUI и
т.д.
Итак, первое, что нам необходимо сделать
– это избавиться от этих зависимостей
Для этого можно использовать уже
знакомые нам mock- и stub-объекты 2-162
54
Учебный Центр Luxoft www.luxoft.ru/edu
Наследованный код и
модульные тесты
Dependency injection
Зачастую, этих средств недостаточно и
необходимо проводить рефакторинг
Здесь на помощь нам приходит паттерн
“Dependency injection”
Суть его заключается в следующем: если
класс А использует класс В, то необходимо
сделать так, чтобы конкретная реализация
класса В передавалась классу А “извне”, а
не определялась внутри него 2-163
Наследованный код и
модульные тесты
Проблемы покрытия legacy-кода
После того, как все связи с внешним
миром у класса или функции оборваны -
мы ее полностью контролируем и можем
полностью проверить ее работу
Для этого может понадобиться создать
еще несколько заглушек, которые будут
выдавать нужные данные для конкретных
тестов
2-164
Наследованный код и
модульные тесты
Проблемы покрытия legacy-кода
В итоге получается, что надо написать
достаточно много дополнительного кода
только для того, чтобы написать первый
тест
Однако для второго теста, такого кода
потребуется уже меньше
«Десятый» тест уже пишется спокойно, с
использованием ранее написанного кода
2-165
55
Учебный Центр Luxoft www.luxoft.ru/edu
План курса
Модуль 1: Модульные тесты
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты
тестирования
2-166
Почему разработчики не
хотят внедрять u-тесты
Слишком простой код
Как правило, так кажется только в момент
его написания
Через полгода (а может и через пару
недель) связи этого кода с другими могут
показаться не такими очевидными, а сам
код – не таким уж и простым
2-167
Почему разработчики не
хотят внедрять u-тесты
Тесты сложнее, чем сам код
Как правило, такая ситуация возникает в
двух случаях:
низкая квалификация разработчика
плохой дизайн тестируемого кода
Возникают ситуации, когда протестировать
класс практически невозможно – напр.
необходимо зарегистрировать
пользователя, добавить данные в БД,
предоставить config-файлы и т.д. 2-168
56
Учебный Центр Luxoft www.luxoft.ru/edu
Почему разработчики не
хотят внедрять u-тесты
Тесты сложнее, чем сам код
Все это говорит о плохом дизайне системы
– классы очень сильно зависят друг от
друга, иногда нетривиальным образом
Новые тесты писать при этом очень
сложно, они часто ломаются
В этом случае стоит начать написание
тестов с простейших классов с
минимальным числом зависимостей
2-169
Почему разработчики не
хотят внедрять u-тесты
Тесты сложнее, чем сам код
Каждый класс, как и его тест, должны быть
небольшими и понятными
После того, как будет покрыт простейший
функционал, можно перемещаться на
более «высокие» уровни системы
Одновременно с этим производить
рефакторинг кода для уменьшения
связности классов
2-170
Почему разработчики не
хотят внедрять u-тесты
Недостаточно времени
Если сравнивать написание классов с
тестами и без, то безусловно, во втором
варианте времени понадобится больше
Однако давайте посмотрим на процесс
разработки и написания тестов более
широко
2-171
57
Учебный Центр Luxoft www.luxoft.ru/edu
Почему разработчики не
хотят внедрять u-тесты
Недостаточно времени
При наличии модульных тестов, пишется
только тот код, который необходим для их
успешного выполнения
Классы становятся меньше по размеру и
выполняют четко определенный набор
функций
Таким образом, пишется меньше кода
2-172
Почему разработчики не
хотят внедрять u-тесты
Недостаточно времени
Разрабатывая небольшими шагами, вы
всегда знаете, что вас ждет дальше – вы
двигаетесь без промедления
Использование unit-тестов существенно
сокращает время, проводимое в
отладчике
добавив небольшую порцию кода, мы тут
же убеждаемся в его работоспособности
2-173
Почему разработчики не
хотят внедрять u-тесты
Недостаточно времени
внеся изменения в классы, мы тут же
убеждаемся, что не нарушили работы
остальных классов
Интеграция модулей проходит легче и
быстрее, поскольку каждый из них хорошо
оттестирован
Уменьшается количество ошибок в коде, а так
же время на их поиск и исправление
2-174
58
Учебный Центр Luxoft www.luxoft.ru/edu
Почему разработчики не
хотят внедрять u-тесты
Недостаточно времени
Более качественный дизайн полученный в
ходе такой разработки, помогает в
дальнейшем улучшать код, делая это
очень быстро
2-175
Почему разработчики не
хотят внедрять u-тесты
Не умеют писать тесты
В этом нет ничего страшного, т.к. не
обязательно сразу начинать тестировать
сложные компоненты, используя все
возможности xUnit и mock-фреймворков
Для начала можно попытаться
воспроизвести в тестах действия, которые
разработчик делает вручную при
тестировании модуля
2-176
Почему разработчики не
хотят внедрять u-тесты
Не умеют писать тесты
Даже небольшая и «неуклюжая» тестовая
обвязка намного лучше, чем еще не
написанная «гибкая система модульного
тестирования, покрывающая 100%
функционала»
Начинайте с простых приемочных тестов
или тестирования классов низших уровней
и продвигайтесь далее в глубь системы
2-177
59
Учебный Центр Luxoft www.luxoft.ru/edu
Почему разработчики не
хотят внедрять u-тесты
Двукратное увеличение кода
По мнению некоторых разработчиков,
модульные тесты – это двукратное
увеличение кода
Это не так
На самом деле оно троекратное (или
даже больше)
Однако давайте рассмотрим и другие
стороны этого вопроса
2-178
Почему разработчики не
хотят внедрять u-тесты
Двукратное увеличение кода
Во-первых, тесты – это код, который
досконально проверяет
работоспособность вашего код
Во-вторых, объем рабочего кода
уменьшается
Однако, при изменении кода, необходимо
изменять и тестирующий код, а это
двойная или тройная работа
2-179
Почему разработчики не
хотят внедрять u-тесты
Двукратное увеличение кода
Классы становятся
узкоспециализированными с четко
определенными интерфейсами
Они берут на себя как можно меньше
обязанностей, но выполняют их хорошо
Снижаются зависимости между классами
Благодаря этому изменения, вносимые в
систему, носят локальный характер
2-180
60
Учебный Центр Luxoft www.luxoft.ru/edu
Почему разработчики не
хотят внедрять u-тесты
Двукратное увеличение кода
Конечно, можно спроектировать систему
так, чтобы изначально обязанности
классов были распределены оптимальным
образом
Однако на практике это практически
невозможно:
никто не может увидеть всей картины
целиком
требования к системе могут меняться 2-181
План курса
Модуль 1: Модульное тестирование
Тестирование как способ обеспечения
качества продукта
Уровни тестирования
Цели и задачи модульного тестирования
Покрытие кода
Унаследованный код
Организационные аспекты тестирования
2-182
Вопросы и ответы
61
Учебный Центр Luxoft www.luxoft.ru/edu
Практикум
Упражнение 1
Разработка приложения по Use-case
диаграмме
Проработка методологии модульного
тестирования
Разработка собственных модульных тестов
2-184
Модуль 2
Каркас JUnit
Рассматриваемые темы:
Введение
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-185
Каркас JUnit
JUnit
JUnit – open-source продукт
JUnit – часть семейства каркасов
модульного тестирования xUnit для
различных платформ
программирования
В разработке JUnit участвовал К.
Бек, идеолог eXtreme Programming
Распространяется одним jar-файлом
junit.jar
2-186
62
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
JUnit как каркас
Специфика библиотеки –
разработчик использует
библиотеку
Специфика каркаса – каркас
использует классы разработчика
2-187
Каркас JUnit
Основные положения JUnit
Сравнение (assertion) ожидаемых и
реальных результатов выполнения
модуля
Фикстуры – разделяемые между
тестами данные и бизнес-логика
Наборы тестов для улучшенной
организации тестирования
Текстовая и графическая среда
запуска тестов
2-188
Каркас JUnit
Основные положения JUnit
Разработчик может тестировать:
> Непосредственно модули или их наборы
> Реализовать сложную бизнес-логику
тестирования
2-189
63
Учебный Центр Luxoft www.luxoft.ru/edu
Основные классы каркаса
Пакет junit.framework
2-190
Ключевые классы
Класс TestCase
Разработчик наследуется от
класса TestCase для создания
своего теста
Класс TestCase реализует шаблон
разработки Template
Это значит, что в нем реализован
каркас бизнес-логики,
ссылающийся на методы,
определенные в потомках 2-191
Ключевые классы
Класс TestCase
Определяет каркас
бизнес-логики
Ссылается на
абстрактные методы,
определяемые в
public void run(){ потомках:
setUp(); > setUp()
runTest(); > Тестовые методы
tearDown(); > tearDown()
}
2-192
64
Учебный Центр Luxoft www.luxoft.ru/edu
Ключевые классы
Класс TestSuite
Разработчик использует класс
TestSiute для формирования
набора тестов
Наборы тестов могут включать в
себя одиночные тесты и другие
наборы тестов
Реализует шаблон разработки
Composite
2-193
Ключевые классы
Класс TestSuite
2-194
Ключевые классы
Отношения ключевых классов
2-195
65
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Рассматриваемые темы:
Введение
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-196
Каркас JUnit
Основные этапы разработки теста
Создать свой класс теста – наследник от
TestCase
В нем определить методы тестирования
вида testXXX()
Создать фикстуры – наборы данных и
логики, используемых при каждом тесте
Возможно, объединить несколько тестов в
набор
Запустить тест (текстовой/графический
интерфейс или в среде разработки)
2-197
Каркас JUnit
Неполный пример теста
1 import java.util.*;
1
import junit.framework.*;
2
2 public class SimpleTest extends TestCase {
3
3 public void testEmptyCollection() {
4
4 Collection testCollection = new ArrayList();
5
5 assertTrue( testCollection.isEmpty());
}
6
6 public static void main( String args[] ){
7
7 junit.textui.TestRunner.run(SimpleTest.class);
}
}
SimpleTest.java
2-198
66
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Рассматриваемые темы:
Введение
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-199
Каркас JUnit
Тестируемый класс
public class IntCalculator {
int plus( int a, int b ) {
return a + b;
}
int minus( int a, int b ) {
return a - b;
}
}
IntCalculator.java
2-200
Каркас JUnit
Тестируемый класс
Сколько модулей мы имеем для
тестирования?
2-201
67
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Класс теста
Класс теста наследуется от класса
TestCase
Класс теста имеет методы
тестирования вида testXXX()
Название методов произвольно,
важен префикс test
2-202
Каркас JUnit
Метод теста
В одном методе теста
разработчик может
протестировать как один модуль,
так и реализовать сложную
бизнес-логику тестирования
нескольких модулей
2-203
Каркас JUnit
Метод теста
В методах тестирования
необходимо сравнивать
полученные результаты
выполнения модуля с
ожидаемыми
2-204
68
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Сравнение
Каркас предлагает специальные
методы сравнения (assertion)
Разработчику теста необходимо
только вызвать их, а результаты
сравнения будут переданы каркасу
автоматически
2-205
Каркас JUnit
Методы сравнения
Сравнение на эквивалентность assertEquals
Сравнение на истинность assertTrue, assert
Сравнение на ложность assertFalse
Сравнение на null assertNull
Сравнение на не-null assertNotNull
Сравнение на идентичность assertSame
Сравнение на не-идентичность assertNotSame
2-206
Каркас JUnit
Методы сравнения
2-207
69
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Методы сравнения
2-208
Каркас JUnit
Методы сравнения
Эти методы сравнения
перегружены для всех
примитивных типов Java и класса
Object
2-209
Каркас JUnit
Пример метода тестирования
1
1 import junit.framework.*;
2
2 public class IntCalculatorTest extends TestCase {
3
3 public IntCalculatorTest(String s){
super(s);
}
4
4 public void testAdd() {
5
5 IntCalculator calc = new IntCalculator();
6
6 assertEquals( calc.plus(2,2), 4 );
}
7
7 public static void main( String args[] ){
8
8 junit.textui.TestRunner.run(IntCalculatorTest.class);
}
}
IntCalculatorTest.jav
a 2-210
70
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Ошибки (failures) и сбои (errors)
Failure – это ошибки сравнений
> Выбрасываются assert-методами
Error – сбои, не связанные со
сравнениями
> Исключения, специфичные для бизнес-
логики программы, а не тестирования
2-211
Каркас JUnit
Ошибки (failures) и сбои (errors)
Failures и Errors передаются
каркасу, и он сигнализирует о них
тестировщику
2-212
Каркас JUnit
Методы тестирования
В некоторых случаях необходимо
явно сигнализировать каркасу о
Failure
Для этого предусмотрены
специальные методы
void fail()
void fail( String s )
2-213
71
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Фикстуры
Фикстура – набор операций,
выполняемых до и после тестов
Они выполняются для каждого
теста testXXX
Каковы задачи фикстур?
2-214
Каркас JUnit
Фикстуры
Фикстуры реализуются методами
setUp()
tearDown()
2-215
Каркас JUnit
Рассматриваемые темы:
Основные классы
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-216
72
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Запуск тестирования
Тестирование можно запустить
как из самого класса теста, так и
из командной строки
Для повышения гибкости следует
использовать запуск из
командной строки
2-217
Каркас JUnit
Запуск тестирования
Каркас предлагает два класса-
launcher для запуска тестов:
> Текстовой launcher
junit.textui.TestRunner
> Графический launcher
junit.swingui.TestRunner
2-218
Каркас JUnit
Пример запуска тестирования
2-219
73
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Пример запуска тестирования
2-220
Каркас JUnit
Пример запуска тестирования
2-221
Каркас JUnit
Пример запуска тестирования
2-222
74
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Рассматриваемые темы:
Основные классы
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-223
Каркас JUnit
Наборы тестов – Tests Suites
Каркас предлагает возможность
объединять тесты в наборы
В наборы можно включать как
одиночные тесты, так и другие
наборы
2-224
Каркас JUnit
Наборы тестов – Tests Suites
Для создания собственного
набора тестов необходимо
наследоваться от класса TestSuite
В нем необходимо определить
статический метод suite(), метод
формирования набора
2-225
75
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Пример набора тестов
import junit.framework.*;
1
1 public class AllCalculatorTests extends TestSuite {
2
2 static public Test suite() {
3
3 TestSuite mySuite = new TestSuite();
4
4 mySuite.addTestSuite( IntCalculatorTest.class );
5
5 return mySuite;
}
}
AllCalculatorTests.java
Какие методы класса теста будут вызваны?
2-226
Каркас JUnit
Запуск наборов тестов
Набор тестов запускается так же,
как и отдельный тест
2-227
Каркас JUnit
Пример запуска наборов тестов
2-228
76
Учебный Центр Luxoft www.luxoft.ru/edu
Каркас JUnit
Пример запуска наборов тестов
2-229
Каркас JUnit
Рассмотренные темы:
Основные классы
Основные этапы разработки
модульных тестов
Простые тесты
Запуск тестирования
Наборы тестов
2-230
Практикум
Упражнение 2
Разработка Junit-тестов в
выработанной методологии
2-231
77
Учебный Центр Luxoft www.luxoft.ru/edu
Рефакторинг
Содержание
Основные понятия
Код с душком
Каталог рефакторингов
Инструменты
Литература
Что это?
это?
Рефакторинг (refactoring) (сущ.) – изменение
(сущ.)
во внутренней структуре программного
обеспечения, имеющее целью облегчить
обеспечения,
понимание его работы и упростить
модификацию, без изменения его
модификацию,
поведения.
поведения.
Проводить рефакторинг (refactor) (глаг.) –
refactor) (глаг.)
изменять структуру програмного
обеспечения, не затрагивая его поведения.
обеспечения, поведения.
78
Учебный Центр Luxoft www.luxoft.ru/edu
Зачем проводить рефакторинг?
рефакторинг?
Улучшает дизайн программного обеспечения
Облегчает понимание программного обеспечения
Позволяет быстрее добавлять новый функционал
Помогает находить ошибки
Позволяет писать код быстрее
Почему рефакторинг дает результаты?
результаты?
Пишите код для сегодня, но думаете про завтра
сегодня,
Трудно модифицировать
> “нечитаемые” программы
> программы с дублированием кода
> программы со сложной логикой условных
операторов
> работающий код, требующий нового
функционала
Безопасный путь модификации программ в легко-
легко-
модифицируемые
Когда проводить рефакторинг?
рефакторинг?
Правило трех
> Когда вы делаете одно и то же в третий раз,
начинайте рефакторинг
При добавлении новой функции
> сделайте код более очевидным
> как должен был бы быть спроектирован код,
чтобы добавить функцию было легко
При исправлении ошибок
> потому что не смогли увидеть ошибку
При разборе кода
79
Учебный Центр Luxoft www.luxoft.ru/edu
Как мотивировать руководителя?
руководителя?
Хороший руководитель (технически-грамотный)
технически- грамотный)
> ориентирован на качество
> хороший способ внедрения – использование при
разборе кода
Руководитель, подгоняемый графиком
Руководитель,
> не говорите ☺
> ему все равно как вы укладываетесь в график
Долги проектирования (когда мы начнем их
оплачивать?)
оплачивать?)
> не удаляется дублирование кода
> ну упрощается код
> не проясняется назначение кода
“Как долго вы не выплачиваете платежи по
кредитам?”
кредитам?
Проблемы при рефакторинге
Не видно, что приводит к росту производительности
видно,
Рефакторинг баз данных
> трудно модифицировать структуру
> миграция данных
> поддержка нескольких версий БД
Рефакторинг XML
> поддержка нескольких версий
> необходимость модификации внешних клиентов
> синхронизация протоколов
Изменения интерфейсов
> опубликованные SDK и API
Изменения архитектуры
> Радикальные изменения трудно/невозможно
воплотить, используя рефакторинг
Когда рефакторинг делать не нужно?
нужно?
Необходимость переписать все с нуля
> когда не удается сделать код устойчивым
Близость срока завершения проекта
> преимущества появится после срока сдачи
80
Учебный Центр Luxoft www.luxoft.ru/edu
Рефакторинг и юнит-тесты
юнит-
Рефакторинг требует юнит-тестов
юнит-
> даже автоматизированные рефакторинги могут
быть источником ошибок
Прежде чем осуществлять рефакторинг
фрагмента, сделайте код самотестирующимся
фрагмента,
Запускайте тесты до и после рефакторинга
Рефакторинг и проектирование
Не является замена предварительному
проектированию
Смещаются акценты
> цель проектирования найти не единственно
правильно решение, а приемлемое
Стремление к простоте
> гибкие решение сложнее
> лучше иметь возможность легко изменять
архитектуру
Код с душком
Метафора для описания мест в проекте, которым
проекте,
возможно требуется переработка
Воспринимается легче, чем “эстетика
легче,
архитектуры”
архитектуры”
Чтобы определить “когда” нужен рефакторинг
когда”
Чтобы определить “когда” остановится
когда”
81
Учебный Центр Luxoft www.luxoft.ru/edu
Дублирование
for (Node childNode = node.getFirstChild();childNode != null;)
{
String value=node.getTextContent();
if(value!=null && value.trim().length()>0)
this.status=value;
Node nextChild = childNode.getNextSibling();
...................
}
........................
for (Node childNode = node.getFirstChild();childNode != null;)
{
String value=node.getTextContent();
if(value!=null && value.trim().length()>0)
this.code=value;
Node nextChild = childNode.getNextSibling();
...................
}
Дублирование
Симптомы
> практически одинаковые фрагменты кода
> фрагменты имеющие одинаковый эффект (на
любом концептуальном уровне)
Как исправить
> выделение метода
> шаблонный метод
> замещение алгоритма
> выделение класса
Длинный метод
public getLDAPClasses()
{
if (serverLDAPConf == null)
throw new LDAPConnectionException("Server Ldap should not be null.");
ldapConnection = init(serverLDAPConf);
// login/password local changing if anonymous
useActualCredentialsIfAnonymous(serverLDAPConf);
ArrayList<String> attrNames = new ArrayList<String>();
Attributes schemaAttrs;
try
{
// it uses getConnectionInfo(), stored in Connection object,
// but sets the Username/Passsword to the new specified values
isAuthenticated = ldapConnection.authenticateUI(user, password);
if (!isAuthenticated)
throw new LDAPConnectionException("Not authenticated.");
schemaAttrs = ldapConnection.doLDAPSchemaSearch(user, password, objectClass);
Attribute mustList = schemaAttrs.get(LDAP_OBJECT_CLASS_MUST_FIELD);
if (mustList != null)
{
addAttrNameToList(attrNames, mustList.getAll());
}
Attribute mayList = schemaAttrs.get(LDAP_OBJECT_CLASS_MAY_FIELD);
if (mayList != null)
{
addAttrNameToList(attrNames, mayList.getAll());
}
Collections.sort(attrNames);
}
catch (NamingException nameEx)
{
Logger.getLogger(this.getClass()).error(ResourceBundleUtils.
getFormatingBundleProperty(AdminResourceBundle.adminLog,
"request-ldap-failed", nameEx.getMessage()));
throw new LDAPConnectionException("Request for LDAP attribute name list failed. See logs.");
}
return attrNames;
}
82
Учебный Центр Luxoft www.luxoft.ru/edu
Длинный метод
Симптомы
> большое количество строк кода в методе
Как исправить
> выделение метода
Большой класс
import javax.swing.event.DocumentListener;
import static java.awt.BorderLayout.*;
................... 500+ imports ...........................
public class ClientFrame extends JFrame
{
private static final String APP_TITLE = "URLyBird booking system";
private static final int CUSTOMER_COLUMN = 6;
................... 30+ constants ...........................
private JPanel searchPanel;
private JTextField hotelLocation = createTextField(20);
private JTextField hotelName = createTextField(20);
private DataSet dataSet = new DataSet(String.class, String.class, Long.class, Boolean.class, Rate.class, Date.class,
String.class);
private DataTableModel model;
private JTable resultTable;
private SelectionGroup selectionGroup = new SelectionGroup();
................... 110+ members ...........................
public ClientFrame(String filename)
{
this();
setUseLiveSearch(true);
setData(openDB.getData(filename));
}
public ClientFrame(String host, int port)
{
this();
setUseLiveSearch(false);
setData(connect.getData(host, port));
}
................... 205+ methods ...........................
}
Большой класс
Симптомы
> большое количество членов класса
> большое количество методов класса
> большое количество строк кода классе
Как исправить
> выделение класса
> выделение подкласса
> выделение интерфейса
83
Учебный Центр Luxoft www.luxoft.ru/edu
Длинный список параметров
/**
* Authenticates agains LDAP v.3 directory
*/
public boolean authenticate (String hostname, String protocol, int port, String username,
String password, Certificate x509, boolean useSsl,
boolean bindAnonymously)
{
..........................................
}
Длинный список параметров
Симптомы
> метод имеет больше 2-4 параметров
Как исправить
> замена параметра вызовом метода
> сохранить целиковый объект
> ввести объект-параметр
Гипотетическая всеобщность
Симптомы
> общие решения для будущего
> реально не используются (“про запас)”
> слишком сложное решение для текущей задачи
Как исправить
> Сворачивание иерархии
> Встраивание класса
> Переименование метода
84
Учебный Центр Luxoft www.luxoft.ru/edu
Цепочки сообщений
Симптомы
> Вызовы типа:
a.b().c().d();
Как исправить
> выделение метода
> перемещение метода
> сокрытие делегата
Комментарии
// creating cert path
// and getting the first issuer
List<X509Certificate> certPathList = new ArrayList<X509Certificate>();
certPathList.add(peerCertificate);
……………………………………………….
if (firstIssuer == null)
{
// we dont't trust the peer cert at all
……………………………….
}
if (!firstIssuer.equals(peerCertificate))
{
// need to search in the trusted CA for the rest of the cert path
X509Certificate curCert = firstIssuer;
……………………………………
}
//remove most-trusted CA from path
if (certPathList.size() > 0)
certPathList.remove(certPathList.size() - 1);
Комментарии
Симптомы
> большое количество комментариев в коде
> “что”, а не “почему”
Как исправить
> выделение метода
> переименование метода
> введение утверждения
85
Учебный Центр Luxoft www.luxoft.ru/edu
Альтернативные классы с разными
интерфейсами
class DateUtils
{
Date nowUtc(int offset)
{
.................
}
}
class Utils
{
Date now()
{
...................
}
}
Альтернативные классы с разными
интерфейсами
Симптомы
> Существует 2 и более реализации одного и
того же, но с разным интерфейсом
Как исправить
> переименование метода
> перемещение метода
> выделение родительского класса
Временное поле
public class Balance
{
private int noOfPeopleInDepartment;
private int noOfPeopleInOfshore;
public calculateBalance(Department dep)
{
noOfPeopleInDepartment=dep.getHeadCount();
..................
}
}
86
Учебный Центр Luxoft www.luxoft.ru/edu
Временное поле
Симптомы
> значение поля устанавливается только в
определенных условиях
Как исправить
> выделение класса
Ленивый класс
class Male extends Human
{
String getType()
{
return "M";
}
}
Ленивый класс
Симптомы
> класс не выполняет никакой работы
Как исправить
> свертывание иерархии
> встраивание класса
87
Учебный Центр Luxoft www.luxoft.ru/edu
Операторы switch
switch(period)
{
case: SUMMER_PERIOD
{
rate=rate*0.7-monthDiscount;
}
case: HOLIDAY_PERIOD
{
rate=rate*0.6;
}
case: NORMAL_PERIOD
{
rate=rate-monthDiscount;
}
}
Операторы switch
Симптомы
> switch выражения
> много идущих подряд if, else-if
> instanceof
Как исправить
> не эмулируйте наследование
> выделение метода
> перемещение метода, замена кода типа
подклассом или стратегией/состоянием
> замена условных выражений полиморфизмом
> замена параметра явными методами
> введение null-объекта
Группы данных
public class Scheduler
{
private Date startOfWorkPeriod;
private Date endOfWorkPeriod;
private Date rateOfWorkPeriod;
private Date startOfSupportPeriod;
private Date endOfSupportPeriod;
private Date rateOfSupportPeriod;
………………………………
}
88
Учебный Центр Luxoft www.luxoft.ru/edu
Группы данных
Симптомы
> одни и те же параметры часто встречаются
вместе как поля классов или аргументы
методов
> группы названий полей содержат одинаковые
подстроки
Как исправить
> выделение класса
> введение объекта-параметра
> сохранение целого объекта
> перемещение метода
Одержимость элементарными типами
final int static MONDAY=1;
final int static TUESDAY=2;
final int static WEDNESDAY=2;
//calculate price in USD
double price=hourRate*hours;
double priceInUSD=price*USD_TO_RUR_RATE;
Одержимость элементарными типами
Симптомы
> использование примитивных типов для работы
со структурными данными
> перечисления и константы представляются с
помощью int
> строковые название представляют названия
полей
Как исправить
> замена значения данных объектом
> замена кода типа классом
> замена кода типа подклассами
> замена кода типа состоянием/стратегией
> выделение классов
> введение объекта-параметра
> замена массива объектом
89
Учебный Центр Luxoft www.luxoft.ru/edu
Классы данных
public class DataClass
{
private Date start;
private Date end;
private double rate;
public Date getStart() { return start; }
public void setStart(Date start) { this.start = start; }
public Date getEnd() { return end; }
public void setEnd(Date end) { this.end = end; }
public double getRate() { return rate; }
public void setRate(double rate) { this.rate = rate; }
}
Классы данных
Симптомы
> содержат только данные и методы для их
получения и установки
> нет поведения
Как исправить
> инкапсуляция поля
> инкапсуляция коллекции
> удаление метода установки значения
> перемещение метода
> сокрытие метода
Посредник
public class Director
{
Calendar scheduler;
public void scheduleMeeting(Date start)
{
scheduler.scheduleMeeting(start);
}
public void scheduleCall(Date start)
{
scheduler.scheduleCall(start);
}
………………………………………….
}
90
Учебный Центр Luxoft www.luxoft.ru/edu
Посредник
Симптомы
> класс делегирует выполнение более половины
методов
Как исправить
> удаление посредника
> замена делегирования наследованием
Отказ от наследства
Симптомы
> подкласс не использует данные или метода
родителя
> использование данных или методов родителя
связано с проблемами
Как исправить
> замена наследование делегированием
> выделение подкласса
> спуск поля
> спуск метода
Завистливые функции
class Processor
{
private Queue executionQueue;
public void scheduleExecutionOrder()
{
int pos=executionQueue.getCurrentPosition();
if(pos=executionQueue.length())
executionQueue.increaseBuffer(POOL_STEP);
executionQueue.add(task);
}
}
91
Учебный Центр Luxoft www.luxoft.ru/edu
Завистливые функции
Симптомы
> метод манипулирует больше данными другого
класса, чем того где объявлен
Как исправить
> перемещение метода
> выделение метода
Расходящиеся модификации
Симптомы
> часто приходится модифицировать класс по
совершенно разным причинам
Как исправить
> выделение класса
> выделение подкласса
> выделение суперкласса
Стрельба дробью
Симптомы
> для внесения каждого изменения необходимо
поменять несколько классов
Как исправить
> встраивание класса
> перемещение метода
> перемещение поля
92
Учебный Центр Luxoft www.luxoft.ru/edu
Рефакторинги
Композиция методов
Перераспределение функций между объектами
Организация данных
Работа с условной логикой
Упрощение вызовов
Обобщение
Композиционные
Выделение метода
Встраивание метода
Встраивание временной переменной
Замена временной переменной вызовом
Введение поясняющей переменной
Разбиение временной переменной
Удаление присваиваний параметрам
Замена метода объектом методов
Замещение алгоритма
Выделение метода (extract method)
Когда: есть фрагмент кода, который можно сгруппировать
for (…..)
{
for (…..) String value=node.getTextContent();
{
String value=node.getTextContent(); if(!isEmpty(value))
this.status=value;
if(value!=null && value.trim().length()>0)
this.status=value; ...................
……………..................... }
}
public boolean isEmpty(String str)
{
return value!=null && value.trim().length()>0;
}
93
Учебный Центр Luxoft www.luxoft.ru/edu
Встраивание метода (inline method)
Когда: тело метода также очевидно как его название
public int getStatus()
{
return (lengthMoreThanLimit(record)) ? 2 : 1;
public int getStatus()
}
{
private void lengthMoreThanLimit(String record) return (record.length()>10) ? 2 : 1;
{ }
return record.length() > 10;
}
Встраивание временной переменной (inline
temp)
Когда: есть временная переменная, которой присваивается
значение один раз, переменная мешает дальнейшим
рефакторингам
public void calculateResult()
public void calculateResult()
{
{
int result = calculator.power(x, y);
return calculator.power(x, y);
return result;
}
}
Замена временной переменной вызовом
(Replace Temp with Query)
Когда: временная переменная используются для хранения
значения выражения
public void balance()
{
if (basePrice() > 1000)
public void balance() return basePrice() * 0.95;
{
else
double basePrice = quantity * itemPrice;
return basePrice() * 0.98;
if (basePrice > 1000) }
return basePrice * 0.95;
else private double basePrice()
return basePrice * 0.98; {
}
return quantity * itemPrice;
}
94
Учебный Центр Luxoft www.luxoft.ru/edu
Введение поясняющей переменной
(introducing explaining variable)
Когда: есть сложное выражение
boolean isMac =
platform.toUpperCase().indexOf("MAC") > -1;
if(platform.toUpperCase().indexOf("MA
C")>-1 && double isIE = browser.toUpperCase().indexOf("IE");
browser.toUpperCase().indexOf("I
E")>-1 && boolean canResize = resize > 0;
wasInitizalized() && resize>0)
{ if(isMac && isIE >-1 && wasInitizalized() && canResize)
......................
{
}
......................
}
Разбиение временной переменной (split
temporary variable)
Когда: есть временная переменная, которой много раз
присваиваются значения, но не переменная цикла и не
накопление результата
public void printInfo()
public void printInfo()
{
double res = 2 * (height + width);
{
System.out.println(res); double perimeter = 2 * (height + width);
System.out.println(perimeter);
res = height * width;
System.out.println(res); double area = height * width;
}
System.out.println(area);
}
Удаление присваиваний параметрам (remove
assignments to parameters)
Когда: код выполняет присваивание параметру
public void doIt(int input,int quantity)
public void doIt(int input,int quantity) {
{ int result=input;
if(quantity>1000)
input=input-2;
if(quantity>1000)
}
result=result-2;
}
95
Учебный Центр Luxoft www.luxoft.ru/edu
Замена метода объектом методов (replace
method with method object) (1)
Когда: есть длинный метод, в котором локальные переменные
не позволяют применить “выделение метода”
class Order
{
public int price(int input)
{
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
int importantValue=primaryBasePrice*quantity*input;
int importantValue2=secondaryBasePrice+quantity*input;
if(input>1000)
importantValue=importantValue-tertiaryBasePrice*input;
int importantValue3=importantValue2+tertiaryBasePrice*quantity;
.........................
}
}
Замена метода объектом методов (replace
method with method object) (2)
class PriceCalculator
{
class Order Order source;
{ int value;
public int price(int input)
{ double primaryBasePrice;
return double secondaryBasePrice;
new PriceCalculator(this, double tertiaryBasePrice;
input,
primaryBasePrice, int importantValue;
secondaryBasePrice, int importantValue2;
tertiaryBasePrice).compute(); int importantValue3;
}
} int compute()
{
.....................
}
}
Замещение алгоритма (substitute algorithm)
Когда: найден более понятный способ решения
public String find(String[] people)
{
for(int i=0;i<people.length;i++)
{ public String find(String[] people)
if(people[i].equals("Don")) {
return "Don"; List candidates = Arrays.asList({"Don", "John", "Ken"});
if(people[i].equals("John"))
return "John";
for (int i = 0; i < people.length; i++)
if(people[i].equals("Mark"))
if(candidates.contains(people[i]))
return "Mark";
} return people[i];
return ""; return "";
} }
96
Учебный Центр Luxoft www.luxoft.ru/edu
Перераспределение функций между
объектами
Перемещение метода
Перемещение поля
Выделение класса
Встраивание класса
Сокрытие делегирования
Удаление посредника
Введение внешнего метода
Введение локального расширения
Перемещение метода (move method)
Когда: метод чаще использует функции другого класса, а не
того где определен
Перемещение поля (move field)
Когда: поле используется другим классом чаще, чем тем в
котором определено
97
Учебный Центр Luxoft www.luxoft.ru/edu
Выделение класса (extract class)
Когда: класс выполняет работу, которую следует разделить на
два класса
Встраивание класса (inline class)
Когда: класс выполняет слишком мало функций
Сокрытие делегирования
(hide delegate)
Когда: клиент обращается к делегируемому классу объекта.
person.getDepartment().getManager()
98
Учебный Центр Luxoft www.luxoft.ru/edu
Удаление посредника (remove middle man)
Когда: клиент слишком занят простым делегированием
Client Class
Person
+getManager()
Department
+getManager()
Введение внешнего метода (introduce foreign
method)
Когда: необходимо ввести в класс дополнительный метод, но
отсутвует возможность модификации класса
Date next=nextDay(previous);
Date next=new Date(previous.getYear(), private static Date nextDay(Date now)
previous.getMonth(), {
previous.getDate()+1);
return new new Date(now.getYear(),
now.getMonth(),
now.getDate()+1);
}
Введение локального расширения (introduce
local extension)
Когда: необходимо ввести в класс несколько дополнительных
методов, но отсутвует возможность модификации класса
Client Class
+nextDay(in Date)
+nextMonth(in Date)
+nextYear(in Date)
99
Учебный Центр Luxoft www.luxoft.ru/edu
Организация данных
Самоинкапсуляция поля
Замена значения данных объектом
Замена значения ссылкой
Замена ссылки значением
Замена массива объектом
Дублирование видимых данных
Замена однонаправленной связи двунаправленной
Замена двунаправленной связи однонаправленной
Замена волшебного числа константой
Инкапсуляция поля
Инкапсуляция коллекции
Замена записи классом данных
Замена кода типа классом
Замена кода типа подклассами
Замена кода типа стратегиями/состоянием
стратегиями/
Замена подкласса полями
Самоинкапсуляция поля (self encapsulated
field)
Когда: обращение к полю происходит непосредственно
private int low,high;
private int low,high;
public boolean includes(int value)
public boolean includes(int value) {
{ return value>=getLow() && value<=getHigh();
return value>=low && value<=high; }
}
public int getLow() {return low;}
public int getHigh() {return high;}
Замена значения данных объектом (replace
data value with object)
Когда: есть элемент данных, для которого требуется
дополнительные данные или поведение
100
Учебный Центр Luxoft www.luxoft.ru/edu
Замена значения ссылкой (change value to
reference)
Когда: есть много одинаковых экземляров класса, которые
требуется заменить одним объектом
Замена ссылки значением (change reference to
value)
Когда: есть маленький неизменяемый и неудобный в
управлении объект ссылки
Замена массива объектов
(replace array with object)
Когда: есть массив, элементы которого представляют собой
разные сущности
String data[] = new String[3]; Perfomance perf=new Perfomance();
data[0] = "Liverpool"; perf.setName("Liverpool");
data[1]="15"; perf.setWins(15);
101
Учебный Центр Luxoft www.luxoft.ru/edu
Дублирование видимых данных (duplicate
observed data)
data)
Когда: есть данные предметной области приложения, которые
существуют только в GUI классах, но к ним нужен доступ
методам предметной области приложения
Замена однонаправленной связи двунаправленной (change
unidirectional association to bidirectional)
Когда:есть два класса, которые должны пользоваться
методами друг друга, но ссылка между ними есть только в
одном направлении
Замена двунаправленной связи однонаправленной (change
bidirectional association to unidirectional)
Когда: имеет двунаправленная связть, но одному из классов
больше не нужны методы второго
102
Учебный Центр Luxoft www.luxoft.ru/edu
Замена волшебного числа константой (replace magic number
with symbolic constant)
Когда: есть массив, элементы которого представляют собой
разные сущности
public double potentialEnergy(double mass,double
height)
{
return mass * 9.81 * height;
}
static final double GRAVITATION_CONSTANT=9.81;
public double potentialEnergy(double mass,double height)
{
return mass * GRAVITATION_CONSTANT * height;
}
Инкапсуляция поля
(encapsulate field)
Когда: есть открытое поле
private int low,high;
public int low,high;
public boolean includes(int value)
public boolean includes(int value) {
{ return value>=getLow() && value<=getHigh();
return value>=low && }
value<=high;
}
public int getLow() {return low;}
public int getHigh() {return high;}
Инкапсуляция коллекции
(encapsulate collection)
Когда: метод возвращает коллекцию
103
Учебный Центр Luxoft www.luxoft.ru/edu
Замена записи классом данных (replace record
with data class)
Когда: требуется взаимодействие со структурой в
традиционном стиле
class DataRecord
{
private int id;
private String name;
struct DataRecord
public DateRecord(int id, String name)
{
int id;
{
String name; this.id = id;
} this.name = name;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name;}
public void setName(String name) { this.name = name; }
}
Замена кода типа классом (replace type code
with class)
Когда: у класса есть числовой тип, который не влияет на его
поведение
Person
1
BloodGroup
-O : BloodGroup
-A : BloodGroup
-B : BloodGroup
-AB : BloodGroup
Замена кода типа подклассами (replace type
code with subclasses)
Когда: имеется неизменяемый код типа, который влияет на
поведение класса
Employee
Engineer Salesman
104
Учебный Центр Luxoft www.luxoft.ru/edu
Замена кода типа стратегиями/состоянием (replace type code
стратегиями/
with state/strategy)
state/strategy)
Когда: имеется неизменяемый код типа, который влияет на
поведение класса, но нельзя применить создание подклассов
Замена подкласса полями (replace subclass
with fields)
fields)
Когда: есть подклассы, которые различаются только
методами, возвращающими данные-константы
Работа с условной логикой
Декомпозиция условного оператора
Консолидация условного выражения
Консолидация дублирующихся условных
фрагментов
Удаление управляющего флага
Замена вложенных условных операторов
граничным оператором
Замена условного оператора полиморфизмом
Введение null-объекта
null-
Введение утверждения
105
Учебный Центр Luxoft www.luxoft.ru/edu
Декомпозиция условного оператора
(decompose conditional)
Когда: имеет сложная цепочка условных проверок (if-then-else)
if(date.before(SUMMER_START) ||
date.after(SUMMER_END)) if(notSummer(date))
charge=quantity*winterRate+winterServiceCharge; charge=winterCharge(quantity);
else else
charge=quantity*summerRate; charge=summerCharge(quantity);
Консолидация условного выражения
(consolidate conditional expression)
Когда: есть ряд проверок, дяющих одинаковый результат
public double accountStatus()
{
if(seniority<2) return 0; public double accountStatus()
if(monthDisabled>12) return 0; {
if(isPartTime) return 0;
if(isNotEligable()) return 0;
......................................
}
}
Консолидация дублирующихся условных фрагментов
(consolidate duplicate conditional fragments)
fragments)
Когда: один и тот же фрагмент кода присутствует во всех
ветвях условного выражения
double total;
if (isDiscountDeal())
{
total = price * 0.94; if (isDiscountDeal())
send(); total = price * 0.94;
} else
else total = price * 0.98;
{
total = price * 0.98;
send();
send();
}
106
Учебный Центр Luxoft www.luxoft.ru/edu
Удаление управляющего флага (remove
control flag)
flag)
Когда: используется переменная, действующая как
управляющий флаг для ряда булевых выражений
boolean done=false; while(true)
{
while(!done)
if(rateLimitExceeded())
{
if(rateLimitExceeded()) {
{ ....
.... break;
done=true; }
}
if(isOverdrawn())
if(isOverdrawn())
{ {
..... .....
done=true; break;
} }
else {........}
else {.....}
}
}
Замена вложенных условных операторов граничным
оператором (replace nested conditionals with guard clauses)
clauses)
Когда: есть условное поведение, из которого неясен
нормальный путь выполнения
double getPayAmount()
{
double result;
if(isDead) result=deadAmount();
double getPayAmount()
else
{
{
if(isSeparated) result=separatedAmount(); if(isDead) return deadAmount();
else if(isSeparated) return separatedAmount();
{ if(isRetired) return retiredAmount();
if(isRetired) result=retiredAmount(); return normalAmount();
else result=normalAmount();
}
}
}
return result;
}
Замена условного оператора полиморфизмом
(replace conditional with polymorphism)
polymorphism)
Когда: есть условный оператор, поведение которого зависит от
типа объекта
double getSpeed()
{
switch(type)
{ Bird
case EUROPEAN:
return baseSpeed(); +getSpeed()
case AFRICAN:
return baseSpeed()-
loadFactor()*cocunuts;
case NOWWEGIAN_BLUE:
return isNailed ? 0 : European African Norwegian Blue
baseSpeed(voltage);
} +getSpeed() +getSpeed() +getSpeed()
throw new UnsupportedException
("Unknown type, curerntly
unsupported");
}
107
Учебный Центр Luxoft www.luxoft.ru/edu
Введение null-объекта (introduce null object)
null-
Когда: есть многкратные проверки на совпадение значения с
null.
if(customer==null)
plan=BillingPlan.BASIC;
else
plan=customer.getPlan();
Введение утверждения(introduce assertion)
утверждения(introduce
Когда: некоторая часть кода предполагает определенное
сотояние программы
double getExpenseLimit()
double getExpenseLimit()
{
{ Assert.isTrue(expenseLimit!=NULL_EXPENSE ||
return (expenceLimit != NULL_EXPENSE) ? primaryProject!=null);
expenceLimit :
return (expenceLimit != NULL_EXPENSE) ?
primaryProject.getMemberExpenseLimit();
}
expenceLimit :
primaryProject.getMemberExpenseLimit();
}
Упрощение вызовов
Переименование метода
Добавление параметра
Удаление параметра
Разделение запроса и модификатора
Параметризация метода
Замена параметра явными методами
Сохранение целого объекта
Замена параметра вызовом метода
Введение объекта-параметра
объекта-
Удаление метода установки значения
Сокрытие метода
Замена конструктора фабричным методом
Инкапсуляция нисходящего преобразования типа
Замена кода ошибки исключением
Замена исключения проверкой
108
Учебный Центр Luxoft www.luxoft.ru/edu
Переименование метода (rename method)
Когда: имя метода не раскрывает его назначения
Добавление параметра (add parameter)
Когда: имя метода не раскрывает его назначения
Удаление параметра (remove parameter)
Когда: параметр больше не используется в теле метода
109
Учебный Центр Luxoft www.luxoft.ru/edu
Разделение запроса и модификатора (separate query from
modifier)
modifier)
Когда: метод возвращает значение и изменяет состояние
объекта
Параметризация метода (parametrize method)
method)
Когда: несколько методов выполняют сходные действия, но с
разными значениями, содержащимися в теле метода
Замена параметра явными методами (replace parameter
with explicit methods)
methods)
Когда: есть метод, выполняющий разный код в зависимости от
значения параметра перечисляемого типа
public void setValue(String name, int value)
{ public void setHeigth(int value)
if (name.equals("height")) {
height = value; heigth = value;
else if (name.equals("width")) }
width = value;
public void setWidth(int value)
throw new
UnsupportedOperationException("Unknown {
parameter."); width = value;
}
}
110
Учебный Центр Luxoft www.luxoft.ru/edu
Сохранение целого объекта (preserve whole
object)
object)
Когда: от объекта получаются несколько значений, которые
потом передаются в метод как параметры
Range range=daysTempRange();
int low=range.getLow();
int high=range.getHigh();
withinPlan = plan.withinPlan(low, high);
withinPlan = plan.withinPlan(dayTempRange());
Замена параметра вызовом метода (replace parameter
with method)
method)
Когда: результат метода передается в качестве параметра в
новый метод
int basePrice=quantity*itemPrice;
discount=discountLevel();
finalPrice = discountedPrice(basePrice, discount);
int basePrice=quantity*itemPrice;
finalPrice = discountedPrice(basePrice);
public int discountedPrice(int basePrice)
{
dicount=discountLevel();
........................
}
Введение объекта-параметра (introduce
объекта-
parameter object)
object)
Когда: есть группа параметров, естественным образом
связанных друг с другом
Customer
+amountInvoicedIn(in DateRange)
+amountReceivedIn(in DateRange)
+amountOverdueIn(in DateRange)
111
Учебный Центр Luxoft www.luxoft.ru/edu
Удаление метода установки значения (remove setting
method)
method)
Когда: поле устанавливается в момент создания и больше
никогда не меняется
Сокрытие метода (hide method)
method)
Когда: метод не используется никаким другим классом
Замена конструктора фабричным методом (replace
constructor with factory method)
method)
Когда: создание объекта подразумевает дополнительные
действия
static Employee create(int type)
{
if(type=ENGINEER)
Employee(int type)
{
return new Engineer();
this.type=type; else if(type=SALESMAN)
} return new Salesman();
throw new IllegalArgumentException("Unknow employee type");
}
112
Учебный Центр Luxoft www.luxoft.ru/edu
Инкапсуляция нисходящего преобразования типа
(encapsulate downcast)
downcast)
Когда: метод возвращает объект, к которому клиент должен
применить нисходящее преобразование типа
Reading=(Reading)lastReading();
Object lastReading() Reading lastReading()
{ {
return return (Reading)readings.lastElement();
readings.lastElement();
}
}
Замена кода ошибки исключением (replace error code
with exception)
exception)
Когда: метод возвращает особый код, представляющий
ошибку
int withdraw(int amount)
{
void withdraw(int amount)
if (amount > balance)
{
return -1;
else if (amount > balance)
{ throw new BalanceException();
balance -= amount;
return 0; balance-=amount;
}
}
}
Замена исключения проверкой (replace
exception with test)
test)
Когда: выбрасывается исключение при невыполнении
предусловия
double getValueOfPeriod(int number)
{
try
{ double getValueOfPeriod(int number)
return values[number]; {
}
if(number >= values.length)
catch(ArrayIndexOutOfBound)
return 0;
{
return 0;
} return values[number];
} }
113
Учебный Центр Luxoft www.luxoft.ru/edu
Обобщение
Подъем поля
Подъем метода
Подъем тела конструктора
Спуск метода
Спуск поля
Выделение подкласса
Выделение родительского класса
Выделение интерфейса
Свертывание иерархии
Формирование шаблона метода
Замена наследования делегированием
Замена делегирования наследованием
Подъем поля (pull up field)
Когда: в подклассах есть одинаковое поле
Employee
-name
Salesman Engineer
Подъем метода (pull up method)
Когда: в подклассах есть методы с идентичными результатами
Employee
Salesman Engineer
+getName() +getName()
114
Учебный Центр Luxoft www.luxoft.ru/edu
Подъем тела конструктора (pull up constructor
body)
body)
Когда: имеются конструкторы с почти идентичными телами
public Manager(String name,String id,int grade)
public Manager(String name,String id,int grade)
{
this.name=name; {
this.id=id; super(name, id);
this.grade=grade; this.grade=grade;
}
}
Спуск метода (push down method)
method)
Когда: в родительском класса есть поведение, относящиеся
только к некоторым из подклассов
Employee
Salesman Engineer
+getQuota()
Спуск поля (push down field)
Когда: есть поле, используемое лишь некоторыми
подклассами
Employee Employee
-quota
Salesman Engineer Salesman Engineer
-quota
115
Учебный Центр Luxoft www.luxoft.ru/edu
Выделение подкласса (extract subclass)
subclass)
Когда: в классе есть функции используемые только в
некоторых случаях
Job Item
+getTotalPrice()
+getUnitPrice()
Labor Item
+getUnitPrice()
+getEmployee()
Выделение родительского класса (extract
superclass)
superclass)
Когда: имеются два класса со сходными функциями
Выделение интерфейса
Когда: несколько клиентов пользуются одним и тем же
подмножеством интерфейса класса или в других классах часть
интерфейса является общей
“interface”
Billable
+getRate()
+hasSpecialSkill()
Employee
+getRate()
+hasSpecialSkill()
+getName()
+getDepartment()
116
Учебный Центр Luxoft www.luxoft.ru/edu
Свертывание иерархии (collapse hierarchy)
hierarchy)
Когда: родительский класс и подкласс мало чем различаюся
Формирование шаблона метода (form template method)
method)
Когда: есть два метода в подклассах, выполняющих
аналогичные шаги в одинаковом порядке, однако сами шаги
различны
return getBaseAmount()+getTaxAmount();
Site
+getBilling()
+getTaxAmount()
+getBaseAmount()
Residential Site
Lifeline Site
+getBaseAmount()
+getTaxAmount()
+getTaxAmount()
+getBaseAmount()
Замена наследования делегированием (replace
inheritance with delegation)
delegation)
Когда: подкласс использует только часть интерфейса
родительского класса или не желает наследовать данные
117
Учебный Центр Luxoft www.luxoft.ru/edu
Замена делегирования наследованием (replace
delegation with inheritance)
inheritance)
Когда: вы пользуетесь делегированием и часто пишете много
простых делегирований для интерфейса в целом
Инструментальные средства рефакторинга
С++
> XRefactory (Emacs, XEmacs)
> *nix, windows, mac
> method extraction;
> namespaces rename,
> Class rename
> Parameters rename,
> Variables rename,
> fields (structure records) and methods
(functions) rename
> parameters insertion, deletion and moving.
Инструментальные средства рефакторинга (2)
С++
> Refactor! For Visual C++ 2005
> Add Block Delimiters
> Case to Conditional
> Conditional to Case
> Create Overload
> Encapsulate Field
> Extract Method (in class)
> Extract Function (outside of class)
> Introduce Constant
> Move Method to Header
> Remove Block Delimiters
> Reorder Parameters
> Move Method to Source File
> Reverse Conditional
> Simplify Conditional
> Widen Scope
> Rename (namespace, type, members, parameters, locals, and
macros)
118
Учебный Центр Luxoft www.luxoft.ru/edu
Инструментальные средства рефакторинга (3)
Delphi 2005
> Rename refactoring
> Refactor driven “Find References”
> Introduce Variable refactoring
> Introduce Field refactoring
> Inline Variable refactoring
> Change Parameters refactoring
> Safe Delete refactoring
> Push Members Up / Down refactoring
> Pull Members Up refactoring
> Extract Superclass refactoring
> Extract Interface refactoring
> Move Members refactoring
> Declare variable refactoring
> Declare field refactoring
> Extract method refactoring
> Find unit/import namespace refactoring
> Refactor driven “Find in Files”
> Extract to resource string refactoring
Литература
Мартин Фаулер, “Рефакторинг, улучшение
Фаулер, Рефакторинг,
существующего кода”
кода”
Джошуа Кириевски, “Рефакторинг с
Кириевски,
использованием шаблонов”
шаблонов”
Стив Макконнелл, “Совершенный код.
Макконнелл, код.
Практическое руководство по разработке
программного обеспечения”
обеспечения”
Вопросы?
Вопросы?
Q&A
119
Учебный Центр Luxoft www.luxoft.ru/edu
Практикум
Упражнение 3
Up-front design VS Simple design
Up-
Подход Test-first + Refactoring
Test-
Еще раз разработать приложение по Use-case
Use-
диаграмме, но с применением подхода Test-first +
диаграмме, Test-
Refactoring
Test Driven Development
Содержание
Основные понятия и принципы TDD
Шаблоны разработки тестов
Общие рекомендации
Литература
120
Учебный Центр Luxoft www.luxoft.ru/edu
Что такое unit-test?
unit-
Программы, которые тестируют другие программы
Программы,
> маленькие
> запускаются наборами
> запускают кусок функционала в заданных
условиях
> проверяют результат или поведение
unit-тесты технически
unit-
Программная среда для написания юнит-тестов
юнит-
> написание тестов
> утверждения
> группировка тестов в наборы
Среда для запуска
> GUI
> встраивание в цикл сборки
> интеграция в IDE
С++
> CPPUnit
Delphi
> DUnit
Что такое TDD?
TDD?
Дисциплина разработки программного
обеспечения
Предсказуемый
> вы знаете когда считать работу законченной
> не беспокоитесь о нескончаемой череде
ошибок
“Чистый код, который работает”
код, работает”
Ваши коллеги могут положится на вас, а вы на
вас,
них
Такой код писать гораздо приятнее ☺
Способ “управления страхом” при разработке
страхом”
программного обеспечения
121
Учебный Центр Luxoft www.luxoft.ru/edu
Почему меня должно это волновать?
волновать?
Но ведь написание тестов это дополнительные затраты
времени?
времени?
Да, иногда тестового кода больше чем работающего, но:
Да, работающего, но:
> вы имеет самотестирующийся код => меньше ошибок
> при достаточно низкой плотности дефектов, QA команда
может перейти от реактивного тестирования к
превентивному
> уменьшается количество неприятных сюрпризов,
менеджерам легче оценивать трудозатраты и
привлекать клиентов к совместной работе
> при низкой плотности дефектов – можно каждый день
получать интегрированную стабильную сборку продукта
> вам легче делать рефакторинг и изменять код
TDD – это для меня?
меня?
TDD не для вас, если
вас,
> вам нравится лепить более-менее работающие
куски кода
> все заработало и вы надеетесь, что вам не
придется возвращаться к этому коду в
дальнейшем
TDD для тех:
тех:
> у кого формируется эмоциональная
привязанность к коду
> кто считает, что чем чище код, тем вероятнее
успех
Как объяснить это руководителю?
руководителю?
Проблема: как убедить выделить время на
Проблема:
написание тестов?
тестов?
Хороший руководитель (технически-грамотный)
технически- грамотный)
> ориентирован на качество
Руководитель, подгоняемый графиком
Руководитель,
> не говорите
> используйте как персональную практику, вы
все равно станете быстрее
122
Учебный Центр Luxoft www.luxoft.ru/edu
Цикл разработки TDD
красный-зеленый-рефакторинг – мантра TDD
красный- зеленый-
(ритм)
ритм)
красный - напишите тест, который не работает
тест,
или даже не компилируется
зеленый – заставьте тест работать как можно
быстрее, напишите ровно столько, чтобы он
быстрее, столько,
сработал
рефакторинг – удалите из написанного все
дублирование
Шаблоны
Шаблоны разработка основанной на тестах
Шаблоны красной полосы
Шаблоны тестирования
Шаблоны зеленой полосы
Шаблоны xUnit
Шаблоны разработки, основанной на тестах
разработки,
Изолированный тест
Список тестов
Вначале тест
Вначале утверждение результата
Тестовые данные
Понятные данные
123
Учебный Центр Luxoft www.luxoft.ru/edu
Изолированный тест (isolated test)
Каким образом исполнение одного теста должно
повлиять на выполнение другого теста?
теста?
> Никаким.
Проблема: ломается что-то одно, а остальные
Проблема: что- одно,
тесты падают по цепочке.
цепочке.
Изоляция позволяет запускать только
подмножество тестов, а не все.
тестов, все.
Список тестов (test list)
Что необходимо тестировать?
тестировать?
Выпишите все тесты, которые вам нужно написать
тесты,
для решения сегодняшних задач
Зафиксируйте их в виде пустых тестов
> не пишите реализацию тестов заранее
Вначале тест (test first)
first)
В какой момент написать тест?
тест?
> перед тем как вы приступите к написанию
нового кода
TDD – методика формирования дизайна вашего
приложения
> как будет использоваться ваш новый код
> когда необходимо остановится
124
Учебный Центр Luxoft www.luxoft.ru/edu
Вначале утверждение результата (assertion
first)
С чего начать написание теста?
теста?
> с оператора assert, который проверяет
результат
> понимание того, что же будет в результате
> начальная точка для размышления
Самоподобие
> с чего начать разработку системы – с
формулировки требований
> с чего начать писать функционал – с теста
Тестовые данные (test data)
data)
Какие данные использовать для тестов?
тестов?
> данные, которые делают тест понятными
> думайте о других
> если нет разницы между 124 и 1, используйте
1 – используйте осмысленные данные
Реалистичные данные
> если вы тестируете реакция на внешние
события
> сравниваете вывод между версиями
программного обеспечения
> изменяете код, имитирующий реальных
процесс
Понятные данные (evident data)
data)
Как отразить в тесте назначение тех или иных
данных?
данных?
> добавьте в тест ожидаемый и реальный
результат и попытайтесь сделать отношения
между ними понятными
> исключение по отношению к “магическим
константам”
> облегчает программирование
Data now=new Date(NOVEMBER_15)
Date result=scheduler.scheduleMeeting(now,
STANDARD_TOLERANCE,
WEEKLY);
assertEquals(NOVEMBER_17,result);
Date result=scheduler.scheduleMeeting(november(15), 5, 7);
assertEquals(november(17),result);
125
Учебный Центр Luxoft www.luxoft.ru/edu
Шаблоны красной полосы
Одношаговый тест
Начальный тест
Тест – объяснение
Тест для изучения
Еще один тест
Регрессионный тест
Одношаговый тест (one-step-test)
one- step- test)
Какой следующий тест выбрать из списка?
списка?
> который чему-либо научит вас
> который вы сможете реализовать
Не делайте большие шаги
Если нет тестов, которые можно реализовать за 1
тестов,
шаг – добавьте новые
Вы всегда должны быть в 1 шаге от зеленой
полосы
Начальный тест (starter test)
С какого теста начать разработку?
разработку?
> начните с варианта операции, которая не
делает осмысленных действий, т.е. не делает
ничего
При написании реалистичного теста нужно
ответить на слишком много вопросов:
вопросов:
> Где должна располагаться операция
> Какие входные данные – корректны
> Каков должен быть результат
Один тест – один ответ
126
Учебный Центр Luxoft www.luxoft.ru/edu
Тест – объяснение (explanation test)
Как распространить в команде использование
автоматического тестирования?
тестирования?
> объясняя что-то, давайте тесты. Спрашивайте
тесты у тех, кто пытается вам что-то объяснить
Тест для изучения (learning test)
test)
Когда необходимо писать тесты для программного
обеспечения, разработанного сторонними
обеспечения,
разработчиками?
разработчиками?
> перед тем как вы впервые воспользуетесь
новыми возможностями
Вы пишите тест, который позволяет вам убедиться
тест,
что API работает так как вы ожидаете
Вы можете производить тестирование на
совместимость с новыми версиями API
Еще один тест (another test)
Как предотвратить уход мысли от основной темы?
темы?
> добавьте постороннюю, интересную мысль в
список тестов и вернитесь к основной теме
127
Учебный Центр Luxoft www.luxoft.ru/edu
Регрессионный тест (regression test)
test)
Что сделать в первую очередь, если был
очередь,
обнаружен дефект?
дефект?
> напишите самый маленький возможный тест,
который его воспроизводит
> сделайте его зеленым
Подумайте о том как бы вы могли узнать о
необходимости написать такой тест до
обнаружения дефекта
Шаблоны тестирования
Дочерний тест
Поддельный объект
Самошунтирование
Логи
Тестирование обработки ошибок
Сломанный тест
Чистый комит
Дочерний тест (child test)
Как заставить работать тест, который получился
тест,
слишком большим?
большим?
> напишите тест меньшего размера, который
представляет собой сломанную часть большого
теста
> сделайте его зеленым
> заново напишите большой тест
Вы сделали слишком большой шаг
> почему?
> как надо было сделать, чтобы тест получился
меньше?
128
Учебный Центр Luxoft www.luxoft.ru/edu
Поддельный объект (mock object)
object)
Как выполнять тестирование объекта, который
объекта,
базируется на тяжеловесном внешнем ресурсе?
ресурсе?
Используйте mock-объекты для эмуляции
mock-
поведение ресурса
> Возможно потребует рефакторинга кода, чтобы
была возможность подменить ресурс на mock-
объект
Mock объекты быстрее
Лучше читаются в тесте
Позволяют сделать изолированный тест
Самошунтирование (self shunt)
Как убедится, что один объект корректно
убедится,
взаимодействует с другим?
другим?
> заставьте тестируемый объект
взаимодействовать с вашим тестом
> возможно придется применить “извлечение
интерфейса”
Альтернатива: используйте mock-объекты
Альтернатива: mock-
Строка-лог (log string)
Строка-
Как убедится, что обращение к методам
убедится,
просиходит в правильном порядке
> создайте строку-лог в классе
> каждый раз при обращении к методу,
добавляйте к ней запись
Альтернатива: используйте mock-объекты
Альтернатива: mock-
129
Учебный Центр Luxoft www.luxoft.ru/edu
Тестирование обработки ошибок (crush test
dummy)
Как протестировать работу кода, который
кода,
обрабатывает ошибку, возникновение которой
ошибку,
маловероятно?
маловероятно?
> создайте специальный mock объект, который
вместо реально работы генерирует ошибку
Сломанный тест(broken test)
тест(
Как завершить сеанс программирования, если вы
программирования,
работаете в одиночку?
одиночку?
> оставьте последний тест неработающим
Когда вы возвращаетесь к работе сразу видите
где вы закончили, и вспоминаете о чем думали
закончили,
Чистый комит
Как следует завершить сеанс программирования,
программирования,
если вы программируете в команде?
команде?
> все ваши тесты должны работать перед
коммитом
Это гарантирует что система находится в каждый
момент в стабильном состоянии
130
Учебный Центр Luxoft www.luxoft.ru/edu
Шаблоны зеленой полосы
Подделка
Триангуляция
Очевидная реализация
От одного ко многим
Подделка (Fake It)
Если у вас есть сломанный тест, какой должна
тест,
быть самая первая реализация?
реализация?
> сделайте чтобы метод возвращал константу
> постепенно трансформируйте константу в
выражение с использованием переменных
Зачем писать “ненужный” код?
ненужный” код?
> Лучше хоть какой-то работающий код, чем
вообще не работающий
> Психология – вы имеете зеленую полосу
> Контроль на объемом работы (программистам
свойственно пере-проектирование)
Триангуляция (triangulate)
triangulate)
Как сформировать абстракцию с помощью тестов?
тестов?
> добавьте несколько тестов с “подделкой”
> попробуйте обобщить код
public void testSum() throws Exception
public void testSum() throws Exception {
{ assertEquals(4,plus(3,1) );
assertEquals(4,plus(3,1) );
} //one more
assertEquals(7,plus(3,4) );
public int plus(int a,int b) }
{
return 4; public int plus(int a,int b)
} {
return a + b;
}
131
Учебный Центр Luxoft www.luxoft.ru/edu
Очевидная реализация (obvious
implementation)
implementation)
Какой способ подходит для реализации простых
операций?
операций?
> просто реализуйте их
Подделка и Триангуляция позволяют вам
двигаться маленькими шажками
> но иногда вы абсолютно уверены как
реализовать функционал
Следите как часто вы сталкиваетесь с красной
полосой
От одного ко многим (one to many)
many)
Как реализовать операцию, которая работает с
операцию,
коллекцией объектов?
объектов?
> реализуйте ее для одного объекта
> затем модифицируйте для работы с
коллекцией таких объектов
Шаблоны xUnit
Утверждения
Фикстура
Тестовый метод
Тест исключения
Все тесты
132
Учебный Центр Luxoft www.luxoft.ru/edu
Утверждения (assertion)
Как убедится что тест работает корректно?
корректно?
> Используйте утверждения
public void testValidateQueue() throws Exception
{
Result res= processor.validateQueue();
assertTrue(res.isValid());
assertEquals(10,res.getStatusCode());
}
Фикстура
Как создаются и освобождаются общие объекты и
ресурсы, используемые в тестах?
ресурсы, тестах?
> конвертируйте локальные переменные в члены
класса теста
> используйте фикстуру для инициализации и
освобождения
Фикстура (2)
private ByteCodeVirefier mockVerifier;
private Processor processor;
public void setUp()
{
mockVerifier = newMock(...);
processor = new Processor(mockVerifier);
}
public void tearDown()
{
resetMocks();
}
public void testValidateQueue() throws Exception
{
Result res = processor.validateQueue();
assertTrue(res.isValid());
assertEquals(10, res.getStatusCode());
}
133
Учебный Центр Luxoft www.luxoft.ru/edu
Тестовый метод
Тест – метод, имя которого содержит название
метод,
тестируемого метода с префиксов test
Обычно располагается в том же
package/namespace что и тестируемый класс
public void testValidateQueue() throws Exception
{
Result res= processor.validateQueue();
assertTrue(res.isValid());
assertEquals(10,res.getStatusCode());
}
Тест исключения (exception test)
Тестируйте исключительные ситуации
> Перехватываете исключение, игнорируете его
> Делаете сбой теста в нормальном потоке
исполнения
public void testQueueFull() throws Exception
{
try
{
processor.validateQueue();
fail("QueueFullException should be thrown");
}
catch(QueueFullException e) {}
}
Все тесты (all tests)
Как запустить все (группы) тестов сразу?
группы) сразу?
> используйте тестовые наборы
class IntegrationTests
{
public static Test suite()
{
TestSuite res = new TestSuite("Integration");
res.addTestSuite(SystemATest.class);
res.addTestSuite(SystemBTest.class);
res.addTestSuite(SystemCTest.class);
return res;
}
}
134
Учебный Центр Luxoft www.luxoft.ru/edu
Когда писать тесты?
тесты?
При добавлении новой функциональности
При изменении существующей функциональности
При исправлении ошибки
Чтобы разобраться в неизвестном коде
Перед рефакторингом
Насколько большим должны быть шаги?
шаги?
Какой объем функциональности должен
покрывать каждый тест?
тест?
> TDD поощряет маленькие шажки
> но не следует доходить до маразма и писать
тест на каждую элементарную строчку
Как много требуется промежуточных стадий для
рефакторинга?
рефакторинга?
> зависит от инструментов
> чем сложнее рефакторинг, тем больше шагов
Сколько должно быть тестов?
тестов?
Обычно тестового кода больше чем рабочего
В TDD прагматичный взгляд на тесты
> цель – работающий код, в корректности
которого мы в достаточной степени уверены
> не пишите тест, если знание реализации
позволяет быть уверенным в корректности
Оценивайте среднее время между сбоями
> Если система должна работать без сбоев до
100 лет, возможно требуется уделить
внимание тестирование самых маловероятных
ситуаций
135
Учебный Центр Luxoft www.luxoft.ru/edu
Когда удалять тесты?
тесты?
Чем больше тестов, тем лучше
тестов,
Если 2 теста кажутся одинаковыми, то
одинаковыми,
> не удаляйте тест, если это снижает вашу
уверенность в корректности поведения
системы
> коммуникация. если другие члены команды
рассматривают два теста как разные сценарии,
то не удаляйте тест.
Литература
Кент Бек, “Экстремальное программирование.
Бек, программирование.
Разработка через тестирование.”
тестирование.
http://www.testdriven.com
http://www.mockobjects.com/
Вопросы?
Вопросы?
Q&A
136
Учебный Центр Luxoft www.luxoft.ru/edu
Практикум
TDD Workshop
Разработка приложения по Use-case диаграмме с
Use-
использованием практик TDD
Две итерации и изменяющимися требованиями
Парная разработка
Шаблоны TDD
137
0 comments
Post a comment