Тесты, которые должен писать разработчик

Artur Basak
10 min readJul 19, 2017

“После каждого исправления ошибки нужно прогнать весь набор контрольных примеров, по которым система проверялась раньше”

Ф. Брукс, Мифический человеко-месяц 1975

Какое-то время назад ко мне обратился менеджер с просьбой подкинуть ему авторитетный ресурс, где была бы описана четкая разница между интеграционными и модульными тестами. Я естественно сказал, что это очевидно и описание подобного можно найти как на Википедии, так и в сотнях ответов на StackOverflow. По-моему, я что-то из этого и скинул в качестве proof.

Через какое-то время менеджер прислал мне сообщение с кратким описанием разницы между тестами, чтобы я перепроверил и дал добро. Я поинтересовался зачем это нужно? Оказалось, один из java-бэкенд разработчиков, которому пытались доказать необходимость таких тестов, стоял на том, что главное это модульные тесты и надобности в интеграционных тестах нет. Времени на прочтение статей на тему у этого разработчика тоже не было. Вот менеджер и решил сформировать лаконичное описание, которое помогло бы изменить его мнение. Я предложил отправить ему картинку, где была бы наглядная разница. Изучить визуальную схему это гораздо быстрее и, возможно, действеннее, чем прочитать статью.

Я пошел искать эту самую картинку. Быстрым гуглением ничего из того, что хотелось бы, обнаружить не получилось. По итогу я сел и сам быстро на своем MacBook в Keynote накидал диаграмму.

Как ни странно, картинка помогла. У разработчика не осталось вопросов. Собственно это и послужило мотивом к написанию этой небольшой заметки с визуализацией разных типов тестов.

Тотальное тестирование системы (TST — Total System Testing)

Мне нравится философия в стиле TST. Нет ненужных тестов. Многие, повторюсь, очень многие разработчики говорят, что у них нет времени на те или иные тесты. Представьте себе доктора, который не сделав и изучив должным образом анализы, выписал вам кучу лекарств и рекомендовал вам принимать их каждый день. Или вы купили автомобиль, который не прошел все необходимые проверки перед эксплуатацией. Или вам надо садиться и лететь на самолете, который должным образом, согласно всем инструкциям, не был протестирован перед полетом. Так себе ситуация, неправда ли?

Всегда нужно стремиться к тотальному и полному покрытию системы тестами. Да, 100% невозможны, но нет ничего плохого в том, чтобы стремиться к этому. Модульные тесты проверяют лишь изолированные и мелкие части системы. Кто проверит их совместную работу? Кто проверит их коммуникацию в связке? Именно системные тесты призваны сделать это. А как же поставка? Делая поставку какого-либо сервиса с публичным API, вы просто обязаны покрыть вызовы этого API интеграционными тестами. В противном случае вам придется нанимать команду ручных тестировщиков каждый раз, как кто-то внесет изменения в систему. И конечно же разработчики скажут, что там ничего не сломалось, все изолированно и этот end-point не задел остальные. Узнаете себя? Но где гарантия? Так вот интеграционные тесты это как раз такая гарантия, гарантия того, что система стабильна.

В тоже время, не стоит забывать, что мы работаем с бизнесом и для бизнеса. В этих реалиях TST невозможно, более целесообразно использовать Пирамиду Тестирования Майкла Коэна или Тестовый Трофей Кента Си Доддса:

Testing Approaches

И помните: автоматизация должна быть похожа скорее на железного человека, нежели на альтрона.

Томас А. Лимонцелли

Ниже я приведу список тестов, которые, по моему скромному мнению, должны писать именно разработчики. Не тестировщик-автоматизатор. Эти ребята дополняют тестовое окружение другими диковинными зверями, такими как тестирование масштабируемости, нагрузочное тестирование, стресс-тестирование, более детальное функциональное и e2e-тестирование и т. д.. Я именно о нас, о разработчиках.

Модульный тест (Unit test, Block test, Component test)

Модульный тест применяется для тестирования одной логически выделенной и изолированной единицы системы. Чаще всего это метод класса или простая функция (хотя я допускаю и весь класс). Изолированность тестируемой единицы достигается при помощи Заглушек (Stubs, Dummies) и Макетов (Mockups).

Модульный тест

Системный тест (System test, Service test)

“Мое практическое правило: 1/3 времени — на проектирование, 1/6 — на написание программы, 1/4 — на тестирование компонентов и 1/4 — на системное тестирование.”

Ф. Брукс, Мифический человеко-месяц 1975

Это комплексный тест, который тестирует связку сразу нескольких компонентов. Система в этом случае воспринимается как черный ящик. Можно сказать, что это модульный тест, где модуль — это связка компонентов. Связка объединена неким фасадом, который предоставляет соответствующий API к ней. Методы этого API — это как раз то, что нам и надо покрыть тестами. Изолированность связки достигается по средствам Заглушек (Stubs, Dummies) и Макетов (Mockups). Факт связанности компонентов и формат коммуникации между ними проверяется с помощью так называемых Шпионов (Spies).

Системный или Сервисный тест

Интеграционный тест (Integration test, Contract test, API driven test)

На самом деле это разновидность системного теста. Этот термин употребляют чаще к тестам, покрывающим непосредственно публичный API сервиса. Фокус устремлен на проверку взаимодействия разных систем по принципу “сервис-клиент”. Т.е. к примеру, методы слоя работы с данными (Data Access Layer) покрываются системными тестами. Методы контроллера, вызывающие функции по вычислению полезной для бизнеса информации (Business Layer), тоже покрываются системными тестами. А вот обработчики HTTP-запросов, которые в свою очередь вызывают методы этих контроллеров, покрываются интеграционными тестами. При таком тестировании запросы должны производится также, как будет это делать конечный клиент этого самого сервиса (к примеру Single-Page Application или тестировщик использующий Postman / Swagger). Это означает, что фактически, для таких тестов необходимо воссоздать почти полноценно работающее окружение. Самое сложное в этом, это как правило, добиться изолированности тестов и сформировать тестовые данные. Для формирования такого окружения применяют шаблоны Испытательная Платформа (TestBed) и Фикстура (Fixture, иногда используют термин Scaffolding).

Интеграционный тест

Функциональный тест (Functional or End-to-End test, GUI test, Walk-Through test)

Тест, который призван полностью эмулировать поведение конечного пользователя системы. Фактически вы должны написать робота, который будет пользоваться вашей системой в тестовом окружении. Чаще этот термин используется применительно к GUI, т.е. взаимодействие пользователя и графического интерфейса системы. Один из самых популярных шаблонов, который позволяет упростить написание таких тестов это Объект-Страница (Page Object, Screen Object). Хорошей практикой считается реализация таких тестов в стиле headless browser, чтобы их можно было прогонять без графического интерфейса, как часть процесса CI (Continuous Integration). Большую часть таких тестов пишут автоматизаторы, но базовый набор должен добавлять разработчик.

Функциональный тест

Снэпшот-тест или снимок (Snapshot test)

Снимок — это вид теста, который находится где-то между интеграционным и функциональным тестом. Чаще всего эти тесты характерны для пользовательского интерфейса и его компонентов, так как проверяют визуальную составляющую. Хотя, такие тесты могут применяться и для других участков системы, к примеру, проверка дампов базы данных или результатов преобразования кода.

Суть теста очень проста, при помощи специализированного инструмента, мы создаем эталонный снимок какого-либо UI компонента или страницы — это, фактически, результат рендеринга.

В тестах, мы монтируем UI компонент с определенными входными данными и, при каждом новом запуске, создаем новый снэпшот. Далее мы сравниваем эталонный снимок и сгенерированный во время работы теста.

Снэпшоты могут быть текстовыми (файл с разметкой HTML, то что умеет делать инструмент Jest) и визуальными (фактически, это скриншот — инструменты вроде Percy, Chromatic или PixelMatch)

Снэпшот-тест или снимок

Проверка работоспособности (Smoke test, Sanity check)

Это частный случай интеграционного теста. Обычно это очень небольшие тесты, которые прогоняются перед запуском системы, чтобы убедиться в работоспособности стороннего ПО, которое необходимо для корректного функционирования нашей системы. В случае провала таких тестов, мы можем оповестить пользователя о проблеме или и вовсе остановить запуск системы.

Такие тесты еще часто называют дымовыми.

Дымовое тестирование — пришло из сферы проверки оборудования, если, после подачи питания, появляется дым и запах гари, то оборудование неисправно.
Философия DevOps. Искусство управления IT, 2017

Смоук тест

Учебный тест (Learning test)

Это разновидность интеграционных тестов, которые пишет клиентский разработчик (интегратор), как часть процесса изучения системы, с которой ему впоследствии придется интегрироваться. Бегло об этом виде тестов упоминают Кент Бекс и Роберт Мартин в своих книгах “TDD: by Example” и “Чистый код” соответственно. Подробнее о них я написал в предыдущей своей заметке:

Учебный тест

Регрессионный тест (Regression test)

Это может быть любой вид теста из описанных выше, который пишется после того, как была обнаружена проблема. Тест должен эмулировать в точности шаги для воспроизведения проблемы. Наличие такого теста после исправления проблемы дает гарантию, что точно такой же баг, больше не появится в системе.

Регрессионный тест

Приемочный тест (Acceptance test, Story test)

Это может быть любой вид теста из описанных выше. Основная идея в том, что один такой тест соответствует конкретной пользовательской истории. Т.е. положительный результат приемочных тестов это гарант того, что вы реализовали функционал именно так, как этого хотел заказчик.

Приемочный тест

Проверка на уязвимости (Penetration test, Pentest)

Проверка системы на различные уязвимости. Хорошим примером будут тесты проверяющие экранирование SQL-команд (защита от инъекций), доступность данных и авторизацию с истекшим токеном и т.д. Сложность в написании таких тестов — это учет всех узких мест. Обычно для этого используют публичную информацию об известных уязвимостях и эксплоитах используемой платформы.

Пентест

Фаззинг тест (Fuzzing test, Fuzztest, Random test)

Чаще разновидность системного теста или проверки на уязвимость. Идея заключается в подаче на вход системы случайного, заведомо некорректного или неожиданного входного потока данных. Целью такого теста является попытка обнаружить нарушения логики валидации и верификации, логики приложения в граничных случаях, внезапные падения сервера, попытки выявить утечки памяти или утечки информации о внутреннем устройстве системы, через необработанные сообщения об ошибках (stacktrace)

Фаззинг тест

Шаблоны

Заглушка (Stub, Dummy) — функция или метод класса, которая подменяет реализацию оригинальной функции и не выполняет никакого осмысленного действия, возвращает пустой результат или тестовые данные.

Stub Diagram

Макет (Mockup) — это может быть экземпляр объекта, который представляет собой конкретную фиктивную реализацию определенного интерфейса. Макет, как правило, предназначен для подмены оригинального объекта системы, исключительно для тестирования взаимодействия и изолированности тестируемого компонента. Методы объекта чаще всего из себя представляют Заглушки (Stubs, Dummies).

Mock Diagram
Crash Test Dummy (example of Mock-Object in Car Design & Testing, wikipedia.org)
An internal view of ESA’s Columbus module training Mockup as example (wikipedia.org)

Шпион (Spy) — Объект-обертка, по типу прокси, которая слушает вызовы и сохраняет информацию об этих вызовах (аргументы, количество вызовов, контекст) оригинального объекта системы. Далее сохраненные шпионом данные используются в тестах.

Astronaut suit sensors as example of Spies in Astronaut Training / Testing

Испытательная Платформа (TestBed) — Это специально воссозданная тестовая среда, можно сказать, платформа для тестирования (может быть комплекс Макетов, Заглушек и Шпионов). Применяется для комплексного тестирования отдельных связок компонентов или всей системы. Также может использоваться, как площадка для экспериментов.

Примерами в JavaScript могут быть: lab и hapi.js server.inject, supertest и express.js приложение, angular 2 testbed для компонентов, enzyme и react-testing-library для react.js компонентов, ну и sandbox в sinonjs.

Bicycle Test Beds from CUBE lab (pinkbike.com)
Bicycle Test Beds from CUBE lab (pinkbike.com)
Aircraft Engine Test Bed (quora.com)

Фикстура (Fixture, Строительные леса / Scaffolding) — Это механизм позволяющий привести объект или всю систему в определенное состояние и зафиксировать это состояние для тестов. Под фикстурой чаще всего понимают тестовые данные необходимые для корректного запуска тестов, а также механизмы загрузки/выгрузки этих данных в хранилище. Т.е. основное назначение фикстур — это привести данные системы к определенному состоянию (фиксированному), которое будет точно известно во время выполнения тестов.

Jet Engine Fixtures for Operational Testing (wikipedia.org)
Test fixture on universal testing machine for three-point flex test (wikipedia.org)

Объект-Страница (Page Object, Screen Object) — Это объект, структура которого повторяет элементы страницы. Объект предоставляет методы по работе с соответствующей UI-страницей (нажатие кнопок, заполнение полей, переход на другие страницы) и методы доступа к информации на этой странице (заголовок, разного рода текст, метки). Один из самых популярных инструментов в этой области это Selenium WebDriver и различные обертки над ним. Сейчас хорошие обороты набрал Puppeteer.

Testing printed circuit boards. Robot Baxter as Human-Tester (youtube.com)

Функции-проверки (Asserts) — Набор функций, позволяющих производить сравнения результатов выполнения двух и более функций. Может предоставлять возможность сравнения структур вглубь, используя механизмы интроспекции проверять у объектов наличие тех или иных свойств.

P.S. А какие типы тестов используете вы? Присылайте ваши диаграммы на artur.basak.devingrodno@gmail.com или в twitter @ArturBasak, мне будет интересно узнать ваши подходы.

--

--