Android:用CircleCi进行自动仪器和单位测试
#测试 #android #cicd #circleci

作为Android开发人员,测试是应用程序开发过程不可或缺的一部分。通过运行测试,您可以在发布给公众之前验证应用程序的功能行为,正确性和可用性。 CircleCi是一个CI平台,可在Android机器图像中支持运行Android模拟器。我们将使用CircleCi为我们的项目设置自动测试。自动化测试涉及使用为您对项目执行测试的工具,该工具更快,可重复,并在开发过程中为您提供有关应用程序的更可行的反馈。

本文介绍了如何使用CircleCi自动化开源Android项目的仪器测试。我认为您已经知道如何创建Android应用程序并为您的应用程序编写测试。可以找到克隆的项目和工作管道。

仪器测试

仪器测试,也称为仪器测试,是在Android设备上进行的测试,无论是物理还是模拟。它涉及通过测试不同组件之间的相互作用来测试应用程序的整体行为。它检查应用程序在不同组件一起工作时的性能,包括用户界面,API和外部依赖项。这种类型的测试有助于识别由于组件之间的相互作用(例如兼容性问题或绩效问题)可能出现的问题。

单位测试

单元测试涉及隔离测试单个组件或代码单元以验证其功能,通常是单个类或功能。在单元测试中,每个代码的每个单元都与代码库的其余部分分开测试,以确保其行为符合预期。

Circlei

CircleCi是一种托管和自动化解决方案,用于连续集成(CI)构建。它使用使用YAML语法的应用程序配置文件。 Circleci更容易设置并托管在云中。在了解了仪器测试,单元测试和CircleCi之后,我们现在可以学习CI/CD中的测试如何工作。

CI/CD中的单元测试和仪器测试

根据DEVOPS原则,最好将测试自动化为CI过程的一部分,以对任何错误测试您的代码。可以在CircleCi管道的各个阶段触发自动测试,包括开发,分期和/或生产。有效的自动化测试是立即进行更改的运行,该测试将对版本控制系统(例如GitHub)登台或开发存储库进行更改。如果测试符合成功标准,则承诺的更改可能会进入下一阶段或环境。如果测试失败,则订立更改将被阻止,直到解决这些问题为止。通过自动测试,团队可以信心对代码更改进行了彻底的测试并达到成功和质量标准,从而保护生产环境免受干扰。

在CircleCi上设置自动化的Android测试

首先,我们将设置config.ymlfile来运行单元测试。我将演示在CircleCi上运行仪器测试的两种方法:使用NO-ORB和ORB示例。 NO-ORB示例是一个更复杂的过程,因为您必须自己编写这些步骤,而Circleci Android Orb示例是一个简单的过程,已经为您编写了许多步骤。无需担心,No-Orb示例并不像看起来那样困难,因为Circleci提供了清晰的documentation,这将使设置变得简单。请注意,您应该只使用config.yml文件中的一个示例。这里给出的两个实例仅用于演示。让我们开始。

创建一个circleci.yml文件

在项目文件夹的根部,创建一个.circleci文件夹。文件夹的内部创建一个config.yml文件。我们的工作流将按以下顺序:

  • 单位测试
  • 仪器测试

运行单元测试

我们将致电我们的工作unit-testsworking_directory指定项目文件的位置。 Docker映像部分指定要使用的Docker映像。在这种情况下,它使用cimg/android:2023.02映像。

这是单位测试工作的全部外观:

jobs:
version: 2.
unit-tests:
    working_directory: ~/project
    docker:
      - image: cimg/android:2023.02
    steps:
      - checkout
      - run:
          name: Run Unit Tests
          command: |
            ./gradlew clean test
        - store_test_results:
            path: app/build/test-results

步骤部分包括三个步骤:第一个CircleCi检查存储代码的存储库。然后,使用./gradlew clean test命令运行单元测试,并将测试结果存储在指定的路径上。

运行仪器测试

正如我之前提到的,这样做有不同的方法。我们将介绍两个例子或方法。第一个示例是No-Orb示例。

使用no-orb示例

我们的仪器测试要求模拟器在机器图像上运行。以下是仪器测试作业的细分。完整的代码如下提供。我们将在circleci.yml文件中的仪器测试job中添加每个步骤。

jobs:
instrumented_test:
    machine:
      image: android:202102-01
    resource_class: large

因此,在仪器测试工作中,我们将image: android:202102-01指定为机器图像,并将机器的resource_class设置为“大”以改善构建时间。在step部分下,我们有以下步骤:

steps:
      - checkout
      - run:
          name: Create avd
          command: |
            SYSTEM_IMAGES="system-images;android-29;default;x86"
            sdkmanager "$SYSTEM_IMAGES"
            echo "no" | avdmanager --verbose create avd -n test -k "$SYSTEM_IMAGES"

- checkout- Create avd下的命令安装模拟器运行所需的android系统映像时,从存储库中检查了代码,并创建了一个名为“ Test”的新的虚拟设备。

- run:
          name: Launch emulator
          command: |
            emulator -avd test -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
          background: true

Launch emulator命令启动模拟器并等待它启动。它在后台运行(背景:true),以便其他步骤可以并行执行。

 - run:
          name: Generate cache key
          command: |
            find . -name 'build.gradle' | sort | xargs cat |
            shasum | awk '{print $1}' > /tmp/gradle_cache_seed

Generate cache key命令根据项目中的build.gradle文件的内容生成一个缓存键。此键用于稍后恢复Gradle Cache,以加快构建过程。

  - restore_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}

restore_cache步骤尝试使用上一个步骤中生成的高速缓存键从上一个构建中恢复Gradle Cache。

- run:
          # run in parallel with the emulator starting up, to optimize build time
          name: Run assembleDebugAndroidTest task
          command: |
            ./gradlew assembleDebugAndroidTest

assembleDebugAndroidTest命令编译并构建Android Test APK的调试版本,并与模拟器启动并行运行,以优化构建时间。

可以修改此步骤以满足您的项目需求。我修改了上述步骤:ð

 - run:
          # run in parallel with the emulator starting up, to optimize build time
          name: Run compileDemoBasicDebugJavaWithJavac task
          command: |
            ./gradlew compileDemoBasicDebugJavaWithJavac

此步骤使用Javac编译器在项目中编译Java代码。这与模拟器启动并行运行,以优化构建时间。

  - run:
          name: Wait for emulator to start
          command: |
            circle-android wait-for-boot

顾名思义,Wait for emulator to startStep等待模拟器完成启动并进行测试。

   - run:
          name: Disable emulator animations
          command: |
            adb shell settings put global window_animation_scale 0.0
            adb shell settings put global transition_animation_scale 0.0
            adb shell settings put global animator_duration_scale 0.0

Disable emulator animations步骤禁用模拟器动画以加快测试过程。

 - run:
          name: Run UI tests (with retry)
          command: |
            MAX_TRIES=2
            run_with_retry() {
               n=1
               until [ $n -gt $MAX_TRIES ]
               do
                  echo "Starting test attempt $n"
                  ./gradlew connectedAndroidTest && break
                  n=$[$n+1]
                  sleep 5
               done
               if [ $n -gt $MAX_TRIES ]; then
                 echo "Max tries reached ($MAX_TRIES)"
                 exit 1
               fi
            }
            run_with_retry

Run UI tests (with retry)在模拟器上运行仪器测试,如果发生故障,则具有重试机制。 connectedAndroidTest命令是运行测试的主命令。

 - save_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}
          paths:
            - ~/.gradle/caches
            - ~/.gradle/wrapper

save_cache使用前面生成的缓存键保存了gradle高速缓存。

让我们看一下整个config.yml文件,其中包括单元测试和仪器测试作业:

jobs:
version: 2.
unit-tests:
    working_directory: ~/project
    docker:
      - image: cimg/android:2023.02
    steps:
      - checkout
      - run:
          name: Run Unit Tests
          command: |
            ./gradlew clean test

      - store_test_results:
          path: app\build\test-results

instrumented_test:
    machine:
      image: android:202102-01
    resource_class: large
    steps:
      - checkout
      - run:
          name: Create avd
          command: |
            SYSTEM_IMAGES="system-images;android-29;default;x86"
            sdkmanager "$SYSTEM_IMAGES"
            echo "no" | avdmanager --verbose create avd -n test -k "$SYSTEM_IMAGES"
      - run:
          name: Launch emulator
          command: |
            emulator -avd test -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
          background: true
      - run:
          name: Generate cache key
          command: |
            find . -name 'build.gradle' | sort | xargs cat |
            shasum | awk '{print $1}' > /tmp/gradle_cache_seed
      - restore_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}
      - run:
          # run in parallel with the emulator starting up, to optimize build time
          name: Run assembleDebugAndroidTest task
          command: |
            ./gradlew assembleDebugAndroidTest
      - run:
          name: Wait for emulator to start
          command: |
            circle-android wait-for-boot
      - run:
          name: Disable emulator animations
          command: |
            adb shell settings put global window_animation_scale 0.0
            adb shell settings put global transition_animation_scale 0.0
            adb shell settings put global animator_duration_scale 0.0
      - run:
          name: Run UI tests (with retry)
          command: |
            MAX_TRIES=2
            run_with_retry() {
               n=1
               until [ $n -gt $MAX_TRIES ]
               do
                  echo "Starting test attempt $n"
                  ./gradlew connectedAndroidTest && break
                  n=$[$n+1]
                  sleep 5
               done
               if [ $n -gt $MAX_TRIES ]; then
                 echo "Max tries reached ($MAX_TRIES)"
                 exit 1
               fi
            }
            run_with_retry
      - save_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}
          paths:
            - ~/.gradle/caches
            - ~/.gradle/wrapper
      - store_test_results:
          path: app\build\outputs\androidTest-results

使用Android Orb示例

由于Circleci已经完成了一些工作,因此此步骤比第一个示例要短一些。因此,您可以使用它而不是no-orb示例,并且它仍然可以使用相同。


version: 2.1
orbs:
  android: circleci/android@2.1.2
jobs: 
  android-test:
    executor:
      name: android/android-machine
      tag: "202102-01"
      resource-class: large
    #run instrumentation tests

    steps:
      - checkout
      - run:
          name: installing emulator and Running Instrumentation tests
          command:  |
            sdkmanager "platform-tools" "platforms;android-29" "build-tools;30.0.0" "emulator"
            sdkmanager "system-images;android-29;google_apis;x86"
            echo no | avdmanager create avd -n test-emulator -k  "system-images;android-29;google_apis;x86"
            emulator -avd test-emulator -noaudio -no-boot-anim -gpu off -no-window &
            adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
            adb shell wm dismiss-keyguard
            sleep 1
            adb shell settings put global window_animation_scale 0
            adb shell settings put global transition_animation_scale 0
            adb shell settings put global animator_duration_scale 0
            ./gradlew connectedAndroidTest

是时候在Circleci上看到我们的CI测试。

如何设置CircleCi
确保您已经在项目文件夹,.circleci文件夹和文件夹内部创建了一个config.yml文件。在转到CircleCi之前,让我们在config.yml文件的开头添加一个构建作业,并在文件末尾定义构建管道的工作流程。使用NO-ORB示例,最终的config.yml文件将看起来像:

version: 2.1
jobs:
  build:
    working_directory: ~/project
    docker:
      - image: cimg/android:2023.02
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ checksum "build.gradle" }}
            - v1-dependencies-
      - run:
          name: Install dependencies
          command: ./gradlew androidDependencies

      - save_cache:
          paths:
            - ~/.gradle
            - ~/.android
          key: v1-dependencies-{{ checksum "build.gradle" }}

      - run:
          name: Build project
          command: ./gradlew clean assemble

unit tests:
    working_directory: ~/project
    docker:
      - image: cimg/android:2023.02
    steps:
      - checkout
      - run:
          name: Run Local UnitTests
          command: |
            ./gradlew clean test

      - store_test_results:
          path: app\build\test-results


  instrumented_test:
    machine:
      image: android:202102-01
    resource_class: large
    steps:
      - checkout
      - run:
          name: Create avd
          command: |
            SYSTEM_IMAGES="system-images;android-29;default;x86"
            sdkmanager "$SYSTEM_IMAGES"
            echo "no" | avdmanager --verbose create avd -n test -k "$SYSTEM_IMAGES"
      - run:
          name: Launch emulator
          command: |
            emulator -avd test -delay-adb -verbose -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim
          background: true
      - run:
          name: Generate cache key
          command: |
            find . -name 'build.gradle' | sort | xargs cat |
            shasum | awk '{print $1}' > /tmp/gradle_cache_seed
      - restore_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}
      - run:
          # run in parallel with the emulator starting up, to optimize build time
          name: Run compileDemoBasicDebugJavaWithJavac task
          command: |
            ./gradlew compileDemoBasicDebugJavaWithJavac
      - run:
          name: Wait for emulator to start
          command: |
            circle-android wait-for-boot
      - run:
          name: Disable emulator animations
          command: |
            adb shell settings put global window_animation_scale 0.0
            adb shell settings put global transition_animation_scale 0.0
            adb shell settings put global animator_duration_scale 0.0
      - run:
          name: Run UI tests (with retry)
          command: |
            MAX_TRIES=2
            run_with_retry() {
               n=1
               until [ $n -gt $MAX_TRIES ]
               do
                  echo "Starting test attempt $n"
                  ./gradlew connectedAndroidTest && break
                  n=$[$n+1]
                  sleep 5
               done
               if [ $n -gt $MAX_TRIES ]; then
                 echo "Max tries reached ($MAX_TRIES)"
                 exit 1
               fi
            }
            run_with_retry
      - save_cache:
          key: gradle-v1-{{ arch }}-{{ checksum "/tmp/gradle_cache_seed" }}
          paths:
            - ~/.gradle/caches
            - ~/.gradle/wrapper
      - store_test_results:
          path: app\build\outputs\androidTest-results

workflows:
  build_and_deploy:
    jobs:
      - build
      - instrumented_test:
          requires:
            - build
      - unit tests:
          requires:
            - build

接下来,使用您的github,bitbucket或gitlab帐户登录到CircleCi。在Circleci Web中,单击侧边栏上的项目,然后选择您希望CircleCi构建的版本控件上的哪个项目。就我而言,我选择了androidlibraryproject。

CircleCI build dashboard
在选择您的config.yml文件模态中,选择最快,弹出窗口将显示为:

CircleCI
最快下,选择选择您的首选项目和项目所在的分支,然后单击设置项目开始在CircleCi上构建您的项目。您可以监视仪表板上的构建过程。在成功的构建中,您将拥有如下:

的绿色版本

CircleCI Dashboard

请注意,仪器测试作业可能要比其他工作要多几分钟。花费的时间完全取决于您的项目。在我的情况下,仪器测试花了将近五分钟的时间来完成建筑物。

CircleCi dashboard showing build time

回顾

在本文中,您学习了如何使用CircleCi为Android项目自动化测试。如何设置单元测试,还学习了两种在CircleCi上设置仪器测试的方法。您已经了解了在CI中自动测试的好处,并且您已经在Circleci上看到了CI测试。