2. Что такое Unit testing?
• Unit testing (Модульное тестирование) – процесс
проверки корректности отдельных модулей
(классов) программы
• Основная идея - разработка тестов, проверяющих
работу каждой нетривиальной функции или метода
модуля
• Для облегчения разработки модульных тестов
используются различные фреймворки
3. TDD (test-driven development)
• Test-driven development (Разработка через
тестирование) – техника программирования, при
которой модульные тесты пишутся до модулей
программы, тем самым управляя их разработкой
• Разработка состоит из коротких циклов (шагов),
продолжительность которых обычно составляет
несколько минут
4. Этапы разработки в стиле
Test-driven development
• Извлечение последней версии проекта из
репозитория
• Добавление теста
• Запуск теста
• Исправление ошибки самым простым способом (в первом
приближении, реализация заглушек – пустых методов)
• Запуск тестов
• Рефакторинг (в том числе трансформация заглушек в реальные
методы)
• Запуск тестов
• Commit изменений
5. Разработка в стиле TDD
Извлечение
кода
Фиксирование
изменений
Добавление
теста
Запуск тестов
Запуск тестов
Внесение
изменений
Рефакторинг
Запуск тестов
6. Пример
Исходный код примера находится в репозитории TFS
$/CSI - Training Center/Clients/Automation/UnitTests/C#
7. Преимущества использования
Unit-тестирования
• Поощрение изменений
• Облегчение рефакторинга
• Легкость обнаружения ошибок
• Упрощение интеграции
• Устранение сомнений по поводу надежности отдельных
модулей
• Документирование кода
• Тесты могут служить иллюстрацией использования
класса
• Отделение интерфейса классов от реализации
• Позволяет уменьшить связность компонентов системы
8. Преимущества использования
Unit-тестирования
• Тесты постоянно управляют процессом разработки
модуля, предъявляя новые и новые формальные
требования к системе
• Написанные однажды тесты служат на протяжении
жизненного цикла программы
• Автоматические тесты выполняются гораздо быстрее
ручных
• Облегчается совместная работа нескольких человек над
одними и теми же модулями
9. Что должны покрывать
модульные тесты?
• Критические и нетривиальные участки кода
• Код, подверженный частым изменениям
• Код, от которого зависит работоспособность
большого количества другого кода
• Сложный код
• Код с большим количеством зависимостей
10. Проблемы, мешающие
внедрению модульных тестов
• Побочные действия кода
• запись данных в файл
• вызов внешних библиотек
• Жесткие зависимости тестируемого класса
• Большое количество зависимостей
• Выполнение одним классом нескольких
обязанностей
Hinweis der Redaktion
Поощрение изменений
Юнит-тестирование позже позволяет программистам проводить рефакторинг, будучи уверенными, что модуль по-прежнему работает корректно (регрессионное тестирование). Это поощряет программистов к изменениям кода, поскольку достаточно легко проверить, что код работает и после изменений.
Упрощение интеграции
Юнит-тестирование помогает устранить сомнения по поводу отдельных модулей и может быть использовано для подхода к тестированию «снизу вверх»: сначала тестируются отдельные части программы, затем программа в целом.
Документирование кода
Юнит-тесты можно рассматривать как «живой документ» для тестируемого класса. Клиенты, которые не знают, как использовать данный класс, могут использовать юнит-тест в качестве примера.
Отделение интерфейса от реализации
Поскольку некоторые классы могут использовать другие классы, тестирование отдельного класса часто распространяется на связанные с ним. Например, класс пользуется базой данных; в ходе написания теста программист обнаруживает, что тесту приходится взаимодействовать с базой. Это ошибка, поскольку тест не должен выходить за границу класса. В результате разработчик абстрагируется от соединения с базой данных и реализует этот интерфейс, используя свой собственный mock-объект. Это приводит к менее связанному коду, минимизируя зависимости в системе.
В идеале, модульные тесты должны покрывать код на 100%, но на практике – это слишком дорого и не слишком эффективно.
Наиболее эффективно покрывать тестами следующий код:
Код, подверженный частым изменениям. Чем чаще код изменяют, тем чаще он ломается. Для такого кода тесты наиболее важны.
Код, от которого зависит работоспособность большого количества другого кода. Такой код, как правило, меняется весьма редко, но неполадки в нем могут привести к тяжело отлавливаемым ошибкам, т. к. на этот код начинают смотреть при отладке в самую последнюю очередь.
Сложный код. Чем сложнее код – тем сложнее будет внести в него изменения не сломав его поведения. Чем лучше такой код покрыт тестами, тем менее квалифицированному программисту можно доверить его изменение.
Код с большим количеством зависимостей по своим свойствам работоспособности очень напоминает часто изменяемый код. Для него также потребуется хорошее покрытие.
Побочные действия кода затрудняют автоматическое тестирование. Так, например, возникновение окон assertion’ов делает тесты неавтоматическими. С выводом в лог мириться проще, однако в записях лога, при модульном тестировании, весьма вероятно наличие большого количества «ложной диагностики» ошибок, которые имитировались в тесте. Вывод в лог однозначно следует глушить, если логами должны пользоваться непрограммисты, им тяжело будет отличить ложные срабатывания от действительных.
Жесткие зависимости мешают оттестировать класс, т. к. не дают заменить ключевые зависимости фальшивыми объектами.
При большом количестве зависимостей класс становится очень трудно протестировать в отрыве от остальной системы. Слишком много требуется фальшивых или настоящих объектов для обеспечения работоспособности класса. Большое количество зависимостей часто говорит о том, что класс берет на себя слишком много функциональности и нуждается в разбиении на более мелкие классы.
Выполнение одним классом нескольких обязанностей затрудняет тестирование функциональности класса. Переплетение функциональностей тяжело распутать снаружи. В результате количество необходимых классу тестов увеличивается в геометрической прогрессии.