Про тестирование мобильных приложений. Часть 4. Интеграционное тестирование |
06.07.2023 00:00 |
Автор: Виталий Никоноров Ранее мы с вами познакомились с 2 группами тестирования, расположенными в основании и на вершине пирамиды тестирования. Пришло время познакомиться с единственной, не рассмотренной на данный момент и наиболее интересной по моему мнению частью – интеграционным тестированием. Перед тем как приступить к рассмотрению самих интеграционных тестов, предлагаю сделать краткое резюме описанного ранее материала и обратить внимание на плюсы и минусы юнит и системных тестов. Для этого проанализируем наиболее типовую на данный момент архитектуру мобильного приложения и попробуем разработать тестовую стратегию, на основе описанных ранее видов тестирования. В ряде случаев некоторые слои или сущности могут не быть представлены отдельными классами или объектами. Однако их роль тем или иным образом все равно будет представлена в приложении (например в случае Redux-подобных архитектур, слои и сущности могут называться немного иначе, но даже такие архитектуры можно Проследим за потоком данных и его трансформациями на конкретном примере. Предположим, мы работаем над экраном регистрации, на котором пользователь вводит свои данные. По нажатию на кнопку Часто бывает, хотя к счастью случается все реже, что команды, особенно те которые только начинают внедрять практику написания тестов, ставят обязательные проверки, чтобы общее тестовое покрытие после каждого изменения ( Допустим, команда работала над этой новой функциональностью, но ближе к финальному сроку условия несколько изменились. Скажем, при регистрации пользователь вводит свой номер телефона, и, при успешном выполнении сервер возвращает модель зарегистрированного пользователя. Предположим, что изначально разработчики согласовали контракт, по которому номер телефона передавался как строка и возвращался в таком же формате. Но ближе к запуску исследователи и продукт пришли к выводу, что нам требуется знать к какой стране относится номер телефона и, для достижения этого, UI скорректировали так, что пользователь выбирает код страны из выпадающего списка и далее по-старому – абонентский номер в виде строки. Таким образом меняется API-контракт. И соответственно все тесты, которые были для этого написаны. Таким образом, если разработчик не предусмотрел такую возможность (что вполне нормально – не следует усложнять реализацию, когда это необязательно), пытаясь предусмотреть все возможные варианты, придется не только изменить код приложения, но также и переписать большое количество unit-тестов. Что на ранних этапах проекта, когда требования могут меняться очень динамично, может стать большой проблемой. (Выдуманная ситуация представлена лишь для примера и несколько гиперболизирована). Попробуем решить эту проблему с помощью системных тестов – напишем e2e тест, который будет имитировать поведение пользователя. В этом случае проблема с изменением требований выглядит не так плохо, достаточно будет лишь изменить пару инструкций в page object экрана и позаботиться о том, чтобы сервер, используемый при тестировании возвращал ответ, соответствующий новому контракту. Однако, как было описано ранее – мы столкнемся со всеми проблемами UI тестов: запуск тестов будет требовать больше ресурсов, занимать больше времени и сигнал, получаемый при их запуске может быть не так стабилен. Выработанный нами подход демонстрирует основные качества юнит и системных тестов: Модульные тестыПлюсы:
Минусы:
Системные тесты:Плюсы:
Минусы:
Как видим, оба рассмотренных вида тестирования имеют ряд плюсов и минусов, которые практически полностью друг друга компенсируют. В идеале, хотелось бы такие тесты, которые можно было бы прогонять также быстро и с наименьшими затратами как и unit-тестов, и в то же время охватить как можно большую часть проекта, как это происходит при системном тестировании. Попробуем найти это самое решение. Для этого можно пойти 2 путями: взяв за основу модульные тесты и постепенно увеличивать их рамки, либо наоборот – начать с системных тестов и решать их проблемы за счет наработок юнит тестов. Постараемся решить проблемы системных тестов, так как они могут быть особенно полезны на начальном этапе жизни проекта, когда требования могут довольно быстро и сильно меняться. Давайте проанализируем их и постараемся найти источник проблем.
По моему опыту, устранение этих 2 факторов может помочь избежать практически все проблемы UI тестов. Так мы можем тестировать нашу систему как черный ящик. Подавать на вход UI event’ы и ответы сервера и валидировать корректность UI State и запросы к серверу. Таким образом мы пришли к одному из видов интеграционного тестирования для мобильных приложений. В классическом понимании, интеграционное тестирование направлено на тестирование целостности потоков данных и производится над системой, компоненты которой полностью или хотя бы основная функциональность которых протестирована модульными тестами. Оно позволяет решить проблемы, которые могут быть вызваны несогласованностью разработчиков, параллельно работающих над разными частями одной системы, некорректными внешними модулями и зависимостями. С развитием сферы программного обеспечения и изменения подходов к разработке, интеграционное тестирование также претерпевает ряд изменений, и сейчас можно выделить 2 подкатегории интеграционного тестирования:
Узконаправленное интеграционное тестирование довольно близко к модульному тестированию. Остановимся подробнее на расширенном интеграционном тестировании и его применении в мобильной разработке. То, какие части включать в интеграционное тестирование может варьироваться от проекта к проекту и от команды к команде (ограничиться только Domain-объектами или Data-моделями, предоставлять моки в виде объектов, строк или файлов и т.п.). На своих проектах я остановился на использовании подготовленных json объектов. Поддерживать валидность этих самых json’ов тоже непростая и интересная задача, которая заслуживает отдельной статьи. Один из возможных вариантов решения – контрактные тесты. Привожу упрощенный пример из того самого предложения про погоду из предыдущих статей. Примеры все также можно найти на GitHub. Упрощенная реализация Api класса, который считывает api ответы из файлов.
Тест:
При желании можно написать намного более fancy реализации фреймворка для настройки теста и окружения, например сделать библиотеку, которая будет по параметрам запроса выдавать подготовленный ответ, написать все на KMM и переиспользовать в iOS проекте, добавить возможность тестирования с разными конфигурациями feature toggle, написать переиспользуемые page object для экранов и много чего еще. Правда следует учитывать, что это тоже код, который тоже может быть источником ошибок :) Несмотря на описанные преимущества, данный вид тестирования имеет ряд недостатков:
Как видите нет серебряной пули, и при выработке тестовой стратегии лучше использовать комбинацию всех описанных видов тестирования. Должна ли тестовая пирамида быть именно пирамидой? Все сугубо индивидуально, очень сильно зависит от проекта, стадии его развития и переиспользования его компонентов другими приложениями и проектами. |