Поднимаем CI на github для Android за день

  • Tutorial
Привет!

С появлением Github Actions проявил инициативу и интегрировал простенький (но вполне эффективный) CI/CD в наш небольшой, но уже как 2 года живой проект Flowwow.

Зачем?



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

Как минимизировать факапы на продакшене, расскажу ниже.

Отчего же лично у меня бывают такие факапы?

  1. Ненадежный участок кода
  2. Завезли какую-то библиотеку и она ситуативно крашит
  3. Обновили какую-то библиотеку (обычно, это аналитика) на нестабильную версию

С 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.

Много полезных примеров можно посмотреть тут и тут.
Надеюсь, статья кому-нибудь будет полезна. Удачи и меньше крашей на продакшене!
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 15

    +1
    Билд происходит на чистом образе Ubuntu? Gradle, Android SDK и все зависимости будут скачиваться заново при каждом билде или можно настроить кэширование?
      0
      Будут скачиваться заново, если не настроить кеширование. Да, его можно настроить!
        0

        Android SDK не будет, он уже установлен. Gradle и все зависимости – будут.

          0
          Да, действительно. Там и Gradle оказывает тоже установлен
            0

            А вот Gradle будет каждый раз скачиваться, т.к. используется Gradle Wrapper через ./gradlew task, а не просто gradle task.

              0
              Я пробовал кэшировать ещё и сами дистрибутивы градла который так скачивается, то практика показала, что так хуже ибо кэш работает не стабильно и в среднем выгоднее всегда скачивать.
        +1
        Добавлю немного инфы которая может стать внезапной, как стало для меня)
        С учётом увеличения популярности Kotlin Multiplatform вы можете захотеть и чтобы CI для iOS также был в GitHub Actions. Если Андроид можно строить и гонять тесты на ubuntu, то iOS только на маках. А время на маках стоит в 10! раз дороже. Т.е. за 5 минут работы CI спишут 50 минут.
        Можно быстро вылететь за пределы, а дальше 8$ за 1000 минут.
          0
          нашли ли вы какую то альтернативу?
            0
            Не искали. Мы разделили workflow, если изменяется iOS директория проекта, то запускается workflow для iOS и наоборот. Плюс также кэшируем, что можем.
            0
            «А время на маках стоит в 10! раз дороже. » — это относится и к минутам, включенным в план?
            В любом случае, альтернативы я не вижу — я искал месяц назад MacOS для CI и обнаружил, что *нет* ни одного провайдера с биллингом хотя бы по часам и API.
              0
              Да, и для включённых в план.
              А Gitlab тоже в маки не умеет?
                0
                У меня ситуация другая — и мой заказчик, и я привыкли к Jenkins и не хотим уходить с него. Одна из апликаций на Electron, и нужно строить-тестировать на Windows & Mac.
                Когда я узнал и поигрался с Github Actions, я надеялся, что смогу использовать его workers, оркестрируя из Jenkins. Но не тут-то было — у Github Actions еще нет API!
                В общем, купили MacMini для CI и поставили в офисе.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое