Никто не спорит с тем фактом, что в процессе разработки необходимо проводить качественное тестирование, которое обеспечит достаточное тестовое покрытие. Но какова область и цель тестов? В какой среде их нужно проводить и как быть с зависимостями?
В статье мы постарались ответить на эти вопросы, рассматривая стратегию тестирования мобильных приложений.
Работа с контекстом и зависимостями
Первым делом необходимо четко определить область и среду для ваших тестов. Представьте, что вы вручную тестируете приложение интернет-магазина. Вы собираетесь протестировать процесс заказа, включая этап оплаты, который предоставляется сторонней компанией? Или вы хотите сосредоточиться только на коде, написанном вашей командой, не затрагивая платежный сервис?
Здесь нет единственного правильного ответа. Но эти два теста нельзя проводить одновременно, и они точно потребуют разных затрат с точки зрения реализации и удобства сопровождения.
Взяв за основу тестовую пирамиду Мартина Фаулера, мы создали свой вариант пирамиды, состоящей из 5 уровней:
Для каждого уровня мы определили:
Цели тестирования: какие компоненты тестируются и в чем состоит цель тестирования?
Область тестирования: будет увеличиваться на каждом этапе пирамиды, что делает тесты более зависимыми от локальных и внешних зависимостей.
Типы тестов: техника реализации тестов.
На каждом уровне тестирования свои сложности. В этом разделе рассмотрим подробнее каждый уровень пирамиды, начиная с нижнего.
Юнит-тесты (Unit Tests)
Юнит-тесты проверяют функциональность самых маленьких фрагментов кода. Область применения — тестируемый класс и сама функция.
Здесь необходимо проверить большинство специфических сценариев и пограничных случаев, которые не обязательно будут проверены на более верхних уровнях.
Внешние зависимости приложения, такие как REST-клиент, должны быть заменены моками.
Локальные зависимости класса, такие как репозиторий, также должны быть заменены моками.
Все современные языки предоставляют фреймворки для юнит-тестирования, такие как XCTest для iOS и JUnit для Android.
Независимо от того, используется ли методология TDD или нет, модульные тесты должны быть частью критериев готовности и быть написаны во время реализации фичи.
Интеграционные тесты (Integration Tests)
С помощью интеграционных тестов проверяют взаимодействие и интеграцию небольших модулей и интерфейсов в рамках одного компонента.
Внешние зависимости приложения, такие как REST-клиент, должны быть сымитированы.
Локальные зависимости внутри тестируемого компонента могут быть использованы по-настоящему, в зависимости от цели теста. Локальные же зависимости вне компонента должны быть заменены моками.
Те же методы, фреймворки и правила повторно используются в юнит-тестах.
Тесты приложения (Application Tests)
Такие тесты фокусируются на приложении целиком, когда можно пройтись по всем уровням тестируемого приложения. Проведение этих тестов гарантирует, что без влияния внешних неблагоприятных воздействий приложение работает так, как ожидается. Это дает уверенность в том, что по крайней мере работает логика внутри приложения.
Внешние зависимости продолжают заменяться моками. Это означает, что в полностью искусственной среде приложение должно работать.
Из-за высокой стоимости внедрения и поддержания таких тестов (обратите внимание на flaky-тесты), тест-кейсы нужно определить тщательно. С этого уровня мы рекомендуем тестировать только те сценарии, которые невозможно проверить с помощью юнит- или интеграционных тестов.
Внедряются чаще всего UI-тесты (XCUITest, Espresso or appium для кроссплатформенности и стратегии черного ящика) или Snapshot-тесты.
Начиная с этого момента и до самых верхних уровней пирамиды, внедрение новых тест-кейсов требует распределения задач. Внедрение новых тест-кейсов должно рассматриваться как отдельная функция.
Системные тесты (System Tests)
Мы почти на вершине нашей пирамиды!
С этого момента можно использовать некоторые внешние зависимости, такие как бэкенд приложения.
Любые подсистемы, которые не являются непосредственной частью конфигурации, например, внешний платежный сервис, нужно заменить моками.
Вам придется решить, запускать ли тесты в выделенной тестовой среде или непосредственно в промежуточной среде. Оба варианта имеют свои плюсы и минусы, сейчас мы их обсуждать не будем.
Вероятно, наиболее используемыми техниками для реализации тестирования на этом уровне являются UI-тесты и Unit-тесты. Но, учитывая вышеприведенный вопрос о среде, их будет сложнее настроить и поддерживать, чем тесты более низких уровней.
Третий вариант — это Consumer Driver Contract Testing (CDCT). Это отличная техника, с помощью которой можно быстро обнаружить любые регрессии между фронтендом и серверной частью. Более подробную информацию можно найти в документации pact.io.
Тестирование системной интеграции (System Integration Tests, SIT)
Наконец, вот и все! Теперь можно полностью использовать любые зависимости нашего приложения с помощью тестов системной интеграции.
Подождите, не расслабляйтесь пока... С большой силой приходит большая ответственность. Вам предстоит узнать цену зависимости от внешних сервисов для тестов. Некоторые из них предоставят вам доступ к выделенной тестовой среде. Если у стороннего сервиса есть доступ только к промежуточной среде или, что еще хуже, только к продуктивной среде, выполнение тестов системной интеграции может стать невозможным.
Несмотря на свою сложность, тестами системной интеграции не стоит пренебрегать. Особенно, если ваш первоначальный план состоял в том, чтобы сократить количество ручных тестов.
Наиболее предпочтительными методами является тестирование пользовательского интерфейса и ориентированные на пользователя контрактные тесты (CDC-тесты).
Главное правило — четко определить, какие сценарии вы хотите протестировать, в каком контексте и в какой среде.
Что принесла нам эта стратегия?
В отделе разработки мобильных приложений Федерального управления информационных технологий, систем и телекоммуникаций (FOITT) мы решили сосредоточиться на задачах в пределах нашей зоны контроля: на нижних уровнях, вплоть до тестирования приложения.
Мы стараемся поддерживать высокий уровень покрытия кода и писать релевантные юнит- и интеграционные тесты — это ответственность команды разработчиков. Эти тесты выполняются в нашем CI-пайплайне при каждом пулл-реквесте.
Фреймворки остаются как можно ближе к технологиям разработки, используемым разработчиками, поскольку это не должно служить ограничением для написания тестов.
Решения касательно сценариев тестирования приложений принимаются всей командой, так как они оказывают большее влияние на нашу работу в целом. Для их выполнения требуется много времени. Поэтому они запускаются в рамках стратегии ночной сборки на облачной платформе, состоящей из реальных устройств. Это дает нам больше уверенности в том, что за предыдущий день не было изменений, приводящих к регрессиям. Также бывало так, что большинство сбоев, о которых QA сообщали во время ручного тестирования, можно было обнаружить в искусственной среде еще на ранней стадии.
Для системных тестов и тестов системной интеграции мы хотели бы в будущем использовать pact.io, поскольку мы его уже применяем в качестве веб-инструментария. Мы также хотели бы избежать необходимости содержать специальную тестовую среду и использовать промежуточную среду напрямую, со всеми вытекающими ограничениями.
Мы продолжаем проводить большое количество ручных тестов и хотели бы их сократить, но заменять все ручные тесты автоматизированными мы не планируем. Предпочитаем увеличить уровень покрытия и количество тест-кейсов на нижних уровнях. Оставляем тесты системной интеграции, системные тесты и даже тесты приложений для типичных критических сценариев нашего приложения.
Рекомендуем ознакомиться с этим тредом на Github, где собрано множество кейсов крупнейших технологических компаний.
Приглашаем всех желающих на открытое занятие «Test IT комбайн для тестировщика». На занятии вы познакомитесь с перспективной отечественной системой для ведения тестовой документации и научитесь создавать кейсы, которые легко поддерживать. Регистрация открыта по ссылке.