С появлением Github Actions проявил инициативу и интегрировал простенький (но вполне эффективный) CI/CD в наш небольшой, но уже как 2 года живой проект Flowwow.
Зачем?
Возможно, есть такие разработчики, которые не совершают ошибок, но вот я — не из таких, поэтому изредка, но случаются вот такие небольшие всплески крашей и приходится в срочном порядке выпускать новую версию с правкой либо откатом к предыдущей версии. Но те часы-дни, в которые пользователи натыкаются на вылеты приложения, без следов не остаются как у клиентов, так и в настроении ответственного разработчика.
Как минимизировать факапы на продакшене, расскажу ниже.
Отчего же лично у меня бывают такие факапы?
- Ненадежный участок кода
- Завезли какую-то библиотеку и она ситуативно крашит
- Обновили какую-то библиотеку (обычно, это аналитика) на нестабильную версию
С 1 пунктом нам помогут code review, Unit тесты, статический анализ кода, UI тесты, ручное тестирование.
Со 2-3 пунктами — только UI тесты и ручное тестирование.
Остается только это автоматизировать. На этом этапе выбор пал на тогда еще только появившийся Github Actions, благо и код проекта находится на Github. Сразу скажу, для free аккаунта github дается бесплатных 2,000 Action минут в месяц.
C чего начать?
Здесь полно готовых примеров для различных языков и фреймворков. Настраивается эта штука через конфигурационный файлик YAML, который находится в репозитории проекта.
Минимальный пример для Android:
name: Android CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew assembleDebug
Описание: на каждый push в любую ветку запускается задача (job) на виртуальной машине github с ОС ubuntu. Шаги задачи: checkout нашего кода, настройка jdk, запуск gradle задачи для сборки.
В случае неуспешного прохождения какого либо шага увидим такую картину
там же можно посмотреть логи.
Удобно, что при Pull Request нам сразу покажут, что наша проверочная последовательность зафейлилась
А если у вас есть интеграция github со Slack, то еще и так
А теперь по пунктам
1. Unit tests
Вы написали Unit тесты используя junit, mockito, etc.
Теперь ваши тесты включаются в последовательность проверки добавлением соответствующей gradle задачи.
- name: Run some unit tests
run: ./gradlew testStageDebugUnitTest
2. Статический анализ кода
Вы можете использовать простые линтеры (detekt — для kotlin, pmd — для java).
Или же более сложный вариант — sonarqube.
В случае простых линтеров (например, у нас и java, и kotlin):
task("checkAll") {
group "Verify"
description "Runs all static checks on the build"
dependsOn "pmd", "detekt"
}
- name: Run some unit tests
run: ./gradlew checkAll
в случае sonarqube — подробнее про настройку — здесь
- uses: actions/checkout@v1
- name: SonarCloud Scan
run: ./gradlew jacocoUnitTestReport sonarqube -Dsonar.login=${{ secrets.SONAR_TOKEN }} --stacktrace
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Link to SonarCloud Report
run: echo "https://sonarcloud.io/dashboard?id=.."
3. UI тесты
Написание UI теста — плод вашей фантазии, мой подход — один «Smoke» тест, имитирующий стандартные действия пользователя в приложении — зайти, выбрать товар, сделать заказ, отследить заказ. Вы можете использовать UIAutomator, Espresso, Kaspresso.
Для запуска здесь тоже 2 варианта — эмулятор на виртуальной машине github или облачные сервисы, такие как Firebase Test Lab
Для использования эмулятора внутри github есть готовые реализации: раз и два.
В случае с Firebase Test Lab, необходимо работать с Google Cloud Platform через gcloud CLI
- name: prepare gcloud
uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
with:
version: latest
service_account_email: ${{ secrets.SA_EMAIL }}
service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: gcloud Set up project
run: |
gcloud config set project ${{ secrets.PROJECT_ID }}
- name: Assemble apks for smoke test
run: ./gradlew Smoke
- name: Run tests in test lab
run: |
gcloud firebase test android run --app app/build/outputs/apk/production/debug/app.apk --test app/build/outputs/apk/androidTest/production/debug/appTest.apk --device model=Nexus6P,version=25,orientation=portrait,locale=en_US --device model=athene,version=23,orientation=portrait,locale=en_US --device model=sailfish,version=26,orientation=portrait,locale=en_US
Чтобы это работало, необходимо создать проект в Firebase, создать в Google Cloud Сonsole сервисный аккаунт с админ правами и полученный ключ json загрузить в base64 в github secrets для авторизации в gcloud.
Общий конфиг в моем случае выглядел так. Задача запускается по событию PR в master
name: Android CI
on:
pull_request:
branches:
- 'master'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Run static checks
run: ./gradlew checkAll
- name: Run some unit tests
run: ./gradlew testStageDebugUnitTest
- name: prepare gcloud
uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
with:
version: latest
service_account_email: ${{ secrets.SA_EMAIL }}
service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
- name: gcloud Set up project
run: |
gcloud config set project ${{ secrets.PROJECT_ID }}
- name: Assemble apks for smoke test
run: ./gradlew Smoke
- name: Run tests in test lab
run: |
gcloud firebase test android run --app app/build/outputs/apk/production/debug/app.apk --test app/build/outputs/apk/androidTest/production/debug/appTest.apk --device model=Nexus6P,version=25,orientation=portrait,locale=en_US --device model=athene,version=23,orientation=portrait,locale=en_US --device model=sailfish,version=26,orientation=portrait,locale=en_US
Вроде, просто. Эффективность зависит от написанных тестов и выбранных правил код-анализа. Можно писать несколько независимых задач (job) для их параллельного запуска. В моем случае все проходит последовательно. Процесс проверки занимает примерно 15 минут на нашем проекте (виртуальная машина github 2-core CPU, 7 GB RAM, 14 GB of SSD), но на самом деле не критично, тк пока «кодревьюишь» глазами, подъезжают результаты и этих тестов.
Больше всего выручают UI тесты — бывает, что во время их прохождения крашится аналитика после обновления библиотеки и ты просто понимаешь, что не стоит её обновлять.
Через gcloud также можно доставлять билды в Firebase App Distribution, релизить в Google Play, etc.
Много полезных примеров можно посмотреть тут и тут.
Надеюсь, статья кому-нибудь будет полезна. Удачи и меньше крашей на продакшене!
anegin
Билд происходит на чистом образе Ubuntu? Gradle, Android SDK и все зависимости будут скачиваться заново при каждом билде или можно настроить кэширование?
allswell Автор
Будут скачиваться заново, если не настроить кеширование. Да, его можно настроить!
ChPr
Android SDK не будет, он уже установлен. Gradle и все зависимости – будут.
anegin
Да, действительно. Там и Gradle оказывает тоже установлен
ChPr
А вот Gradle будет каждый раз скачиваться, т.к. используется Gradle Wrapper через
./gradlew task
, а не простоgradle task
.agent10
Я пробовал кэшировать ещё и сами дистрибутивы градла который так скачивается, то практика показала, что так хуже ибо кэш работает не стабильно и в среднем выгоднее всегда скачивать.