Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
фото с Unsplash по запросу "pipeline"
Общий подход
Привет! Я начинаю серию постов о пайплайнах в разработке и не только, которые помогают удостовериться в качестве разрабатываемых мобильных приложений. Главная идея в том, чтобы осветить все подходы к мобильной разработке, актуальные сейчас: нативную разработку для Android и iOS, React Native, Xamarin и Flutter. Я начну с Android, но сначала хотел бы дать общее представление, о чём это всё.
Имейте в виду, что это общий обзор инструментов и практик, способных пригодиться вам в процессе разработки Android-приложений, а не туториал по настройке этих инструментов.
Ради чего это всё?
Начнём с общеизвестной истины: чем позже находишь дефект в приложении, чем дороже его исправить. Предположим, что вы обнаружили баг уже в продакшне. Вашим тестировщикам надо воспроизвести этот баг локально, зарепортить его, приоритезировать, передать разработчикам, а им, в свою очередь, надо исправить его, сделать новую сборку, передать обратно тестировщикам, чтобы они убедились, что всё исправлено, а затем сделать релизную сборку, и только после этого отправить новую версию пользователям.
Много часов труда можно было бы сэкономить, если бы у вас были механизмы, предотвращающие появление багов в самом начале. Я не говорю, что есть серебряная пуля, избавляющая от всех багов — это невозможно. Но что возможно, так это воздвигнуть барьеры (также известные как quality gates), повышающие вероятность отловить баги в вашем коде как можно раньше.
На компьютере разработчика
Вы как разразботчик всегда работаете на своей машине с IDE и инструментами командной строки. Давайте посмотрим, что существует для Android-разработчиков.
Android Studio
Это сейчас вариант по умолчанию для Android-разработчика, и поскольку он основан на платформе IntelliJ, там есть множество инспекций для Java, Kotlin и XML. Советую договориться в команде о конкретных правилах, которые вы хотите использовать, сконфигурировать их на одном компьютере и закинуть файл settings.jar с этими правилами в вашу систему контроля версий или какой-либо инструмент коллаборативной работы (вроде Confluence).
Настройки инспекций в Android Studio
В AS также есть быстрые исправления, которые можно применить к своему коду по нажатию Alt+Enter.
Пример быстрого исправления от Android Studio
Android Lint
Это инструмент статического анализа конкретно для Android-разработки, «из коробки» снабжённый сотнями правил, любые из которых вы можете использовать. Lint может и запускаться из Gradle-задачи (task), и давать подсказки прямо в Android Studio, и генерировать отчёт. У него множество проверок, разделённых по категориям — безопасность, интернационализация, юзабилити, производительность...
Но особенно мощным его делает возможность добавлять свои собственные правила. Например, у Room есть свой набор правил, у библиотеки логирования Timber — свой. Можно создать правила для вашей команды или проекта и быть уверенным, что никто не совершает определённые типичные ошибки. (Кстати, скоро на конференции Mobius будет доклад об этом, объясняющий и теоретическую сторону, и практическую).
Пример отчёта от Android Lint
Ещё статический анализ
Конечно, есть много инструментов статического анализа, предназначенных не конкретно для Android, а в целом для Java и Kotlin: PMD, FindBugs (заброшен, используйте SpotBugs), Checkstyle, Ktlink, Detekt и другие. Выберите себе по душе, интегрируйте его в свой пайплайн и обеспечьте его реальное использование (как именно? читайте дальше).
Пример отчёта от FindBugs
Но недостаточно наличия инструмента, предоставляющего данные о том, что надо поправить. Вам также пригодится следующая информация:
- Как изменяется со временем покрытие кода тестами?
- Сколько времени мне потребуется для исправления всех найденных проблем?
- Сколько в проекте дублирующегося кода?
- Как мне распространить свои правила на несколько команд?
И многие другие. Мы в EPAM Systems уделяем внимание качеству, поэтому выбрали SonarQube как инструмент для ответов на эти вопросы. О других преимуществах SonarQube можно узнать здесь.
Юнит-тестирование
Надеюсь, не требуется вновь убеждать вас, что вашему коду, чёрт возьми, требуются юнит-тесты по различным причинам. Не имеет значения, следуете ли вы TDD, придерживаетесь принципа пирамиды тестирования или новой идеи гриба тестирования. Не так важно, какой уровень покрытия вы ставите целью, в любом случае ваши юнит-тесты — необходимая составляющая. А значит, вам надо их писать и запускать! К счастью, за 11 лет эволюции мы получили довольно удобный механизм запуска тестов из Gradle и Android Gradle-плагина.
В андроид гредл-проекте по умолчанию есть задача testDebugUnitTest (у вас она, естественно, может отличаться в зависимости от флейворов), и именно она запускает тесты. Убедитесь, что её используют и что покрытие кода со временем растёт. Плюс Java юнит-тестов в том, что они работают на JVM рабочей станции, а не на Dalvik/ART, что даёт преимущество в скорости.
В случае с андроидными юнит-тестами есть одна фундаментальная проблема: зависимость от Android SDK. Это одна из причин, по которым появились все эти подходы к UI-слою вроде MVP, MVVM, MVI и прочие MV*. Конкретная проблема в зависимости класса от Android в том, что юнит-тесты просто падают из-за использования стабов этого самого Android, которые просто бросают исключение. Конечно, существует пара вариантов: либо пропустить тесты для этого класса, либо извлечь часть логики в другие классы, либо создавать интерфейсы для Android-зависимых классов для тестирования какой-то высокоуровневой логики, либо использовать Robolectric (что далеко от идеала). Другой вариант — использовать инструментированные тесты, которые могут подходить для проверки поведения Activity, но современный тренд в том, что Activity не должна содержать тестов.
К вопросу о покрытии: вы хотите знать, какое оно у вас в данный момент и как меняется со временем, так что вам пригодился бы инструмент и для этого. Насколько мне известно, jacoco — индустриальный стандарт для Java, и у него есть поддержка Kotlin.
Инструментированные тесты
Эти тесты запускаются на эмуляторе Android, что позволяет им использовать Android Framework. К сожалению, они медленные и нестабильные (из-за некоторых проблем с эмулятором), так что большинство известных лично мне разработчиков старается их избегать. Но их поддержка есть в Gradle и Android Studio, так что для вас они, может, и подойдут.
Аудит безопасности
Помимо простых ошибок, опечаток, проблем при копипейсте и тому подобного, есть ещё и большая категория проблем, на которую вам стоит обращать внимание: безопасность. Конечно, Android Lint уже предоставляет некоторые соответствующие подсказки, но лучше озаботиться специализированными инструментами конкретно для этой задачи. Эти инструменты могут работать как в статическом, так и в динамическом режимах; в зависимости от ваших требований к безопасности вы можете хотеть использовать как один из этих режимов, так и оба. Возможно, вы захотите начать, например, с Mobile Security Framework, а позже рассмотреть платные варианты.
К счастью, есть список инструментов статического анализа, составленный непосредственно OWASP. Например, можете выбрать Find Security Bugs или использовать OWASP SonarCube Project.
Удостовериться в выполнении проверок
Как я уже сказал, чем короче цикл фидбека, тем меньше времени и денег вы тратите на исправление багов. Так что хочется быть уверенным, что код соответствует продакшн-качеству ещё до того, как он попадёт в репозиторий или даже будет закоммичен локально. Конечно, можно просто попросить своих разработчиков выполнять проверки, но есть вариант куда лучше: Git hooks.
Я предлагаю добавить pre-commit hook для всех проверок, которые мы обсудили выше: Lint, статического анализа кода и юнит-тестов. Пример процесса настройки можно найти здесь.
CI/CD-пайплайн
Очень сложно представить Android-проект без CI/CD-пайплайна. Ваша цель — повторить все вышеописанные проверки ещё и на этапе сборки. На то есть несколько причин:
- Git hooks можно легко обойти с помощью параметра '--no-verify'
- Код может успешно проходить все проверки локально, но вносить проблемы уже после merge
- Вам нужны отчёты о тестах и покрытии
Пример отчёта на bitrise.io
К счастью, вам достаточно или упомянуть эти проверки безопасности непосредственно в билд-скрипте Gradle, или вызывать соответствующие задачи в вашем CI/CD-пайплайне. Если у вас есть сложности с выстраиванием пайплайна, недавно на конференции DevOops я выступил с докладом о мобильном DevOps в 2019-м.
Пожалуйста, делайте также следующее:
- Прогоняйте все проверки для пулл-реквестов. Не позволяйте мерджить ни один реквест, нарушающий любое из правил. Это очень важно: если правило не выполняется, то оно, по сути, не существует.
- Прогоняйте все проверки при сборке и деплое. Вы не хотите понижать свою планку качества.
- Если билд сломался — это первоочередная задача. Команда должна исправить проблему немедленно, потому что она нарушает вашу практику continuous delivery и не даёт команде писать качественный код.
И удачи в улучшении своего кода!
Если вам понравилась эта статья, подписывайтесь на меня в Твиттере, чтобы не пропустить следующие. А если вы в декабре будете в Москве или можете приехать, приходите на нашу конференцию Mobius и узнайте много другого о разработке для Android (и iOS)!
P.S. Спасибо vixentael за подходы к вопросам безопасности, Алексею Никитину за ревью и комментарии, Александру Баканову за пруфридинг.