Android 基于 GitLab + Docker 自动化

1,264 阅读5分钟

1. GitLab CI 基础概念

1.1 使用前提

官方指南文档:gitlab.sensorsdata.cn/help/ci/qui…

在开始使用 GitLab CI/CD 之前,需要确保:

  • 确保具有可用的 runners 执行器。如果没有,则需要进行安装或者注册执行器
  • 创建 .gitlab-ci.yml 文件配置执行脚本

执行器 Runner 查看: Settings > CI/CD 然后点击 expand Runners

1.2 基础概念

1.2.1 安装并注册 GitLab Runner

GitLab CI/CD 的构建中,需要 GitLab Runner 执行器用于执行我们的脚本配置。所以首先我们需要创建 Runner

Homebrew 安装 GitLab Runner

brew ``install gitlab-runner

这里就会执行安装程序,一般中途没有错误就表示安装成功了。下一步进行 Runner 注册

gitlab-runner register

Runner 注册的过程中,会出现多个输入:

  • Enter the GitLab instance URL:输入你的 GitLab 地址
  • Enter the registration token:输入项目下的 Token
  • Enter a description for the runner:设置 Runner 的描述
  • Enter tags for the runner (comma-separated):设置 RunnerTag 标记,这个在后面我们指定执行 Runner 时有用
  • Enter an executor: virtualbox, docker+machine, docker, parallels, shell, docker-ssh+machine, kubernetes, custom, docker-ssh, ssh:选择执行器,一般可选 shelldocker

image.png 查看项目对应的 Token 和 GitLab 地址的方式:Project Settings Page > CI/CD > Runner,然后单击展开按钮。 image.png 最后完成注册后,我们可以通过 gitlab-runner verify 指令查看 Runner 状态。

删除 Runner

gitlab-runner verify --delete --name runner的标记

1.2.2 镜像 image

image 关键字是 Docker executorDocker 映像的名称,用于运行 CI/CD 作业。默认情况下,Docker 执行器从 Docker Hub 中下载镜像,当然我们也可以指定本地镜像。

使用格式:
image: <image-name> (Same as using <image-name> with the latest tag)
image: <image-name>:<tag>
image: <image-name>@<digest>
 
示例:
image: runmymind/docker-android-sdk

1.2.3. 关键字

job 的脚本中,有以下关键字。

KeywordDescription
after_scriptOverride a set of commands that are executed after job.
allow_failureAllow job to fail. A failed job does not cause the pipeline to fail.
artifactsList of files and directories to attach to a job on success.
before_scriptOverride a set of commands that are executed before job.
cacheList of files that should be cached between subsequent runs.
coverageCode coverage settings for a given job.
dast_configurationUse configuration from DAST profiles on a job level.
dependenciesRestrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from.
environmentName of an environment to which the job deploys.
exceptControl when jobs are not created.
extendsConfiguration entries that this job inherits from.
imageUse Docker images.
includeInclude external YAML files.
inheritSelect which global defaults all jobs inherit.
interruptibleDefines if a job can be canceled when made redundant by a newer run.
needsExecute jobs earlier than the stage ordering.
onlyControl when jobs are created.
pagesUpload the result of a job to use with GitLab Pages.
parallelHow many instances of a job should be run in parallel.
releaseInstructs the runner to generate a release object.
resource_groupLimit job concurrency.
retryWhen and how many times a job can be auto-retried in case of a failure.
rulesList of conditions to evaluate and determine selected attributes of a job, and whether or not it's created.
scriptShell script that is executed by a runner.
secretsThe CI/CD secrets the job needs.
servicesUse Docker services images.
stageDefines a job stage.
tagsList of tags that are used to select a runner.
timeoutDefine a custom job-level timeout that takes precedence over the project-wide setting.
triggerDefines a downstream pipeline trigger.
variablesDefine job variables on a job level.
whenWhen to run job.

1. stages 关键字

用于定义 jobs 任务中的全局执行状态,通常默认的 pipelines 状态包含:.prebuildtestdeploy.post

注意在同一个状态的任务脚本并行执行。

2. workflow 关键字

通常配合 rules 关键字用于预设值。

workflow:
  rules:
    - if: $CI_COMMIT_MESSAGE =~ /-draft$/
      when: never
    - if: '$CI_PIPELINE_SOURCE == "push"'

3. script 关键字

用于指定需要执行的命令指令。所有的 job 任务都需要这个关键字。

job1:
  script: "bundle exec rspec"
 
job2:
  script:
    - uname -a
    - bundle exec rspec

4. before_script 关键字

用来指定 job 所有的 script 指令执行前应该执行的配置。

job:
  before_script:
    - echo "Execute this command before any `script:` commands."
  script:
    - echo "This command executes after the job's `before_script` commands."

5. after_script 关键字

用来指定 job 所有的 script 指令执行后应该执行的配置。

job:
  script:
    - echo "An example script section."
  after_script:
    - echo "Execute this command after the `script` section completes."

6. only 关键字

配合内部一些关键字来指定执行的 branchpilepipe 等。

job1:
  script: echo 'test'
 
job2:
  script: echo 'test'
  only:
  - branches
  - tags

7. when 关键字

定义 job 任务执行的时机。

  • on_success (default): Run the job only when all jobs in earlier stages succeed or have allow_failure: true.
  • manual: Run the job only when triggered manually.
  • always: Run the job regardless of the status of jobs in earlier stages.
  • on_failure: Run the job only when at least one job in an earlier stage fails.
  • delayedDelay the execution of a job for a specified duration.
  • never: Don't run the job.
cleanup_build_job:
  stage: cleanup_build
  script:
    - cleanup build when failed
  when: on_failure

8. artifacts 关键字

用于指定 job 任务执行成功或失败时的产物文件或文件夹。

build:linux:
  stage: build
  script: make build:linux
  artifacts:
    paths:
      - binaries/

2. Android 自动化单元测试

2.1 环境配置

在使用 gitlab-ci + docker 配置的环境时,需要使用对应的 android 编译环境镜像。常见的镜像有:

image: jangrewe/gitlab-ci-android
image: runmymind/docker-android-sdk
image: sprheany/docker-android

对应的仓库:

2.1.1 配置 runner 缓存

在服务器上找一个文件夹挂载到 docker 容器里边,给 .gradle 做一个缓存,这样每次编译的时候,就不用一直下载 gradle 了,这里我挂载的是 /home/android-cache 文件夹:

vi /etc/gitlab-runner/config.toml

参照文档:blog.csdn.net/Captive_Rai…

2.2. Android 单元测试

Android 中的单元测试可以分为两类:本地单元测试和插桩单元测试。

  • 本地测试:运行在本地的计算机上,这些测试编译之后可以直接运行在本地的 Java 虚拟机上(JVM)。可以最大限度的缩短执行的时间。如果测试中用到了 Android 框架中的对象,那么谷歌推荐使用 Robolectric 来模拟对象。
  • 插桩测试:在 Android 设备或者模拟器上运行的测试,这些测试可以访问插桩测试信息,比如被测设备的 Context,使用此方法可以运行具有复杂 Android 依赖的单元测试。

本地单元测试是 src/test 文件下的测试用例,插桩测试执行 src/androidTest 文件下的内容。

  • app/src/androidTest 主要是运行在真实手机或者模拟器上的测试,比如集成测试,端到端测试,以及仅靠JVM无法完成的功能验证测试
  • app/src/test 在本地计算机上运行的测试,比如单元测试

image.png

Android 工程目录:

app/src
 ├── androidTest/java (仪器化单元测试、UI测试,比如Espresso)
 ├── main/java (业务代码)
 └── test/java  (本地单元测试,Junit4、mockito、Robolectric)

这里需要注意对于本地单元测试使用的依赖是 testImplementation,而插桩单元测试使用的依赖是 androidTestImplementation

关于单元测试的介绍,可以阅读 Android 官方的指南:developer.android.com/training/te…

2.2.1 本地单元测试

本地单元测试用适用于不需要在真实设备上运行测试关联度较低的场景,使用本地单元测试可以更快速来检测我们的代码逻辑。通常使用 junit、Robolectric 框架来实现。

1. 搭建测试环境

Android 项目中,本地单元测试的源文件存储目录为:module-name/src/test/java/。一般当我们创建项目时就已经存在了。同时我们也需要为单元测试添加依赖框架,在应用的 build.gradle 文件中添加依赖:

dependencies {
    // Required -- JUnit 4 framework
    testImplementation 'junit:junit:4.12'
    // Optional -- Robolectric environment
    testImplementation 'androidx.test:core:1.0.0'
    // Optional -- Mockito framework
    testImplementation 'org.mockito:mockito-core:1.10.19'
}

2. 创建本地单元测试类

使用 JUnit4 编写测试类,只需要在测试方法前面添加 @Test 注解即可。

import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Test;
 
public class JUnitTestDemo {
 
    @Test
    public void trackEventValid() {
        MatcherAssert.assertThat("str1", false);
        Assert.assertEquals("str1","st");
    }
}

在比较结果时,可以使用 junit.Assert 方法或 Hamcrest 匹配器来完成。

3. 添加框架依赖

在测试的过程汇总,通常会涉及到一些与 Android 系统依赖的工作,这里就需要借助 Robolectric 框架完成,该框架用于在本地 JVM 模拟真实的 Android 框架代码中对象。

import android.content.Context;
 
import androidx.test.core.app.ApplicationProvider;
 
import org.junit.Assert;
import org.junit.Test;
 
public class JUnitTestDemo {
    private Context context = ApplicationProvider.getApplicationContext();
    @Test
    public void trackEventValid() {
       String pageName = context.getPackageName();
       Assert.assertEquals(pageName, Package.getPackage(pageName).getName());
    }
}

2.2.2 插桩单元测试

插桩单元测试是在实体设备或模拟器上运行的测试,这类测试可以更多利用 Android API,测试的保真度比本地单元测试高。EspressoUI Automator 就是这类测试,Espresso 一般用来测试单个界面,UI Automator 一般用来测试多界面交互。它们运行的比本地测试慢很多,所以谷歌建议最好是必须针对设备测试的时候才使用。

1. 搭建测试环境

Android 项目中,插桩单元测试的源文件目录在 module-name/src/androidTest/java/ 中。当项目创建完成时则存在。在开始之前,我们需要添加 AndroidX Test 相关的依赖。

dependencies {
    androidTestImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test:rules:1.1.0'
    // Optional -- Hamcrest library
    androidTestImplementation 'org.hamcrest:hamcrest-library:1.3'
    // Optional -- UI testing with Espresso
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    // Optional -- UI testing with UI Automator
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}

如需使用 JUnit 4 测试类,请务必在您的项目中将 AndroidJUnitRunner 指定为默认插桩测试运行程序,方法是在应用的模块级 build.gradle 文件中添加以下设置:

android {
    defaultConfig {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

2. 创建插桩单元测试类

插桩单元测试类是一个 JUnit4 测试类,需要指定执行器。

import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
 
import org.junit.Test;
import org.junit.runner.RunWith;
 
@RunWith(AndroidJUnit4ClassRunner.class)
public class InstrumentationTest {
 
    @Test
    public void createLog() {
 
    }
}

2.3. Android 编译配置

首先需要在项目的根目录中创建一个 .gitlab-ci.yml 文件,然后填写对应的执行脚本。

image: runmymind/docker-android-sdk
 
before_script:
  - chmod +x ./gradlew
 
stages:
  - build
 
assembleDebug:
  stage: build
  script:
    - ./gradlew clean
    - ./gradlew assembleDebug
  artifacts:
    paths:
    - app/build/outputs/
  only:
    - master
 
assembleRelease:
  stage: build
  script:
    - ./gradlew clean
    - ./gradlew assembleRelease
  artifacts:
    paths:
    - app/build/outputs/
  only:
    - master

不使用镜像,自己下载安装。



image: openjdk:8-jdk
# 定义变量
variables:
  ANDROID_COMPILE_SDK: "28"
  ANDROID_BUILD_TOOLS: "28.0.2"
  ANDROID_SDK_TOOLS:   "4333796"
# 安装包,安装 Android SDK,配置环境
before_script:
  - apt-get --quiet update --yes
  - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
  - wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip
  - unzip -d android-sdk-linux android-sdk.zip
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null
  - echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null
  - export ANDROID_HOME=$PWD/android-sdk-linux
  - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
  - chmod +x ./gradlew
  # temporarily disable checking for EPIPE error and use yes to accept all licenses
  - set +o pipefail
  - yes | android-sdk-linux/tools/bin/sdkmanager --licenses
  - set -o pipefail
# 定义状态
stages:
  - build
  - test
# 编译 app
lintDebug:
  stage: build
  script:
    - ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint
 
assembleDebug:
  stage: build
  script:
    - ./gradlew assembleDebug
  artifacts:
    paths:
    - app/build/outputs/
# 执行测试
debugTests:
  stage: test
  script:
    - ./gradlew -Pci --console=plain :app:testDebug

2.4. Android 编译检查

2.4.1. lint 工具检查

Lint 工具是 Android Studio 自带的检查工具,可以方便检查一些针对 Android 的问题。更详细的使用可以参照:gudong.site/2021/01/17/…

./gradlew lint

2.4.2. 代码覆盖率

./gradlew clean createDebugCoverageReport

2.4.3. 模拟器

模拟器的启动可以通过 emulator 指令。

emulator -avd avd_name [ {-option [value]} … ]

对于从 Android Studio 启动时,一般的指令格式如下:

/Users/antway/Library/Android/sdk/emulator/emulator -avd Nexus_5X_API_23 -netdelay none -netspeed full

如需查看 AVD 名称的列表,请输入以下命令:

emulator -list-avds
与此同时,我们可以在对应的 SDK 目录文件夹下面查看对应的模拟器 Android 系统镜像。
Mac OS X 和 Linux - ~/Library/Android/sdk/system-images/android-apiLevel/variant/arch/

查看 AVD 信息。AVD 数据目录(也称为内容目录)特定于单个 AVD 实例,包含 AVD 的所有可修改数据。

Mac OS X 和 Linux - ~/.android/avd/name.avd/

2.4.4 其它检测指令

check - 运行所有检查.

connectedAndroidTest - 在已连接的设备上安装所有flavors(渠道包)并运行instrumentation测试

connectedCheck - 在当前已连接的设备上运行设备检测.

connectedDebugAndroidTest - 在已连接的设备上安装并运行Debug版本的测试.

deviceAndroidTest - 在所有提供的设备上安装并运行instrumentation测试.

deviceCheck - 在所有提供的设备和测试服务器上运行设备检测.

lint - 在所有变种版本上运行lint检测.

lintDebug - 在Debug版本上运行lint检测.

lintRelease - 在Release版本上运行lint检测.

test - 在所有变种版本上运行单元测试.

testDebugUnitTest - 在Debug版本上运行单元测试.

testReleaseUnitTest - 在Release版本上运行单元测试

参考文章