Raising CI on github for Android in a day

Hello!

With the advent of Github Actions, he took the initiative and integrated the simple (but quite effective) CI / CD into our small, but already 2 years old, live Flowwow project .

What for?



Perhaps there are some developers who do not make mistakes, but here I am not one of them, so occasionally, but such small bursts of crash happen and you have to urgently release a new version with editing or rollback to the previous version. But those hours-days in which users stumble upon app crashes do not remain without traces both among customers and in the mood of a responsible developer.

How to minimize fakapy on production, I will tell below.

Why do I personally have such fakap?

  1. Insecure code snippet
  2. They brought in some library and it crashes situationally
  3. Updated some library (usually analytics) to an unstable version

With 1 point, code review, Unit tests, static code analysis, UI tests, manual testing will help us.

With 2-3 points - only UI tests and manual testing.

It remains only to automate this. At this stage, the choice fell on the then just appeared Github Actions , the benefit and the project code is on Github. I must say right away, for a free github account, there are 2,000 free action minutes per month.

Where to start?


It is full of ready-made examples for various languages โ€‹โ€‹and frameworks. This thing is configured through the YAML configuration file , which is located in the project repository.



Minimal example for 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

Description: for every push, a task is launched on any branch on the github virtual machine with ubuntu OS. Task steps: checkout our code, configure jdk, run gradle tasks for assembly.

In case of unsuccessful passage of a step, we will see such a picture



there you can see the logs.

Itโ€™s convenient that with Pull Request weโ€™ll immediately be shown that our test sequence has flopped.



And if you have github integration with Slack, then itโ€™s also



Now point by point


1. Unit tests

You wrote Unit tests using junit, mockito, etc.

Now your tests are included in the test sequence by adding the appropriate gradle task.

- name: Run some unit tests
  run: ./gradlew testStageDebugUnitTest

2. Static code analysis

You can use simple linters ( detekt - for kotlin, pmd - for java).
Or a more complicated option is sonarqube .

In the case of simple linters (for example, we have both java and kotlin):

task("checkAll") {
    group "Verify"
    description "Runs all static checks on the build"
    dependsOn "pmd", "detekt"
}

- name: Run some unit tests
  run: ./gradlew checkAll

in case of sonarqube - more about tuning - here

- 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 tests

Writing a UI test is a figment of your imagination, my approach is one โ€œSmokeโ€ test that imitates the standard actions of the user in the application - log in, select a product, place an order, track the order. You can use UIAutomator, Espresso, Kaspresso.

There are also 2 options for launching here - an emulator on a github virtual machine or cloud services such as the Firebase Test Lab.
To use the emulator inside github, there are ready-made implementations: one and two .

In the case of the Firebase Test Lab, you must work with the Google Cloud Platform through the 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

For this to work, you need to create a project in Firebase, create a service account with admin rights in Google Cloud Console, and upload the received json key to base64 in github secrets for authorization in gcloud .

The general config in my case looked like this. The task is triggered by the PR event in 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

It seems simple. Efficiency depends on the written tests and the selected code analysis rules. You can write several independent tasks (job) to run them in parallel. In my case, everything goes sequentially. The verification process takes about 15 minutes on our project (virtual machine github 2-core CPU, 7 GB RAM, 14 GB of SSD), but itโ€™s not really critical, as long as you "code it" with your eyes, the results of these tests also arrive.

UI tests help out the most - it happens that during their passage the analytics crashes after updating the library and you just understand that you should not update it.

Through gcloud, you can also deliver builds to Firebase App Distribution, release to Google Play, etc.

Many useful examples can be seen here and here .
I hope this article is useful to someone. Good luck and less crashes on production!

All Articles