用CircleCI构建一个Android应用程序并测试

2022-11-06  本文已影响0人  蜗牛是不是牛

CircleCI在Android开发者中很受欢迎,原因有几个:它可以快速上手,快速执行你的构建,具有很高的并行性,(无论是本地,跨平台或多平台),甚至支持直接从CircleCI与我们的Android机器图像运行Android模拟器。

本文将向您展示如何在CircleCI平台上为一个示例项目构建和测试Android应用程序。

要从这篇文章中获得最大的收获,你应该有

CircleCI的安卓版有什么可用?

我们已经为你提供了一些构建Android的工具。

Docker镜像

Docker镜像是团队使用的主要执行工具。它在Docker容器中运行,所以启动起来非常快,并且包含了你可能需要的所有东西,可以开始构建Android项目。镜像还预装了Node.js、Android NDK和浏览器工具的变体。

机器图像

最大的变化是新的Android机器镜像。这是一个虚拟机镜像,所以比上面提到的Docker镜像需要更多的时间,但它包含了所有你可能需要的工具,以构建你的Android应用程序,包括SDK、Google Cloud的Firebase工具、Python、Node.JS和Fastlane。 然而,最大的变化是对嵌套虚拟化的支持,它允许你在这个机器镜像中运行Android模拟器。

安卓机器镜像支持嵌套虚拟化,这允许在CircleCI作业中运行x86模拟器。如果你从安卓平台的早期就开始开发,你可能记得模拟器非常慢。然后,x86模拟器开始支持英特尔的Hyper-V管理程序,这让模拟器直接使用主机的资源。像这样直接使用资源,使得模拟器能够更流畅、更快速地运行。

在很长一段时间里,支持仅限于我们的本地硬件。对CircleCI的有限支持拖慢了仿真器的速度,并使其难以运行UI测试。嵌套的虚拟化解决了这个问题,它将对快速仿真的支持添加到一个大规模的可重复的CI/CD环境中,使其所有的好处都可以得到。

Android Orb

最后是CircleCI的Android Orb,它提供了对作为执行器的图像以及安装SDK、运行仿真器、缓存、测试等方便的工作和命令的轻松访问。

介绍一下Android CI演示项目

我创建了一个演示项目,它是谷歌自己的安卓测试Codelab来源的一个分叉。Codelab是一个简单的TO-DO列表管理器应用程序,包含了单元测试和仪器测试的组合,有和没有UI。如果你在Android Studio中打开它,确保切换到Project 视图。项目视图可以让你查看我们要审查的.circleci/config.yml 文件。

该项目有一个单一的app 模块,有默认的debugrelease 变体。test 目录包含单元测试,而androidTest 目录包含仪器测试。完整的CircleCI配置包括在内,我将在本文中提到它。该配置位于.circleci/config.yml

构建和测试Android应用样本的CI

我们的CI过程必须做三件事。

  1. 在一个虚拟机内运行单元测试
  2. 在仿真器上运行仪表测试
  3. 构建应用程序的发布版本

我们将并排运行单元测试和仪器测试。如果这两种测试都成功了,我们就可以组装发布版的构建。我们还将添加一个条件,即只有在新的工作被提交到 "main "分支,并且在从23到30的每个Android版本的模拟器测试成功后,才能继续发布构建。当我们的构建满足这个条件时,我们将有坚实的保证,我们的应用程序将在许多不同的Android设备上运行。

注意: 正如我之前提到的,从这一点开始,一切都将参考CircleCI的配置文件,在.circleci/config.yml

使用CircleCI Android orb

创建CI管道的第一步是使用Android orb,它包含许多已经为你预写的步骤。

在定义了轨道后,脚本指定了这些工作。

一个工作流程,test-and-build ,也包括在内。

version: 2.1

orbs:
  android: "2.1.2"

jobs:
  unit-test:
    ...
  android-test:
    ...
  release-build:
    ...
    # We'll get to this later

workflows:
  test-and-build:
    ...
  # We'll get to this later
复制代码

运行单元测试

我们的unit-test 工作包含一个来自Android orb的执行器:android/android-docker 。如前所述,这是在Docker容器中运行的,启动速度非常快。

steps 章节中,有几件事需要注意。首先,我们运行android/restore-gradle-cache Gradle缓存帮助存储我们的依赖性和构建工件。在运行测试命令后,我们还运行android/save-gradle-cacheandroid/save-build-cache ,以确保后续的构建通过使用缓存运行得更快。这些都来自Android orb,正如前缀android/ 所示。

我们还可以通过将find-args 参数传递给restore-gradle-cache ,来修改被缓存的内容。

主要的构建步骤发生在android/run-tests 命令中,我们在一个test-command 参数中调用./gradlew testDebug 。这个命令为debug 构建变量运行所有单元测试。作为奖励,这个命令带有一个默认的重试值,以帮助你避免测试的失误。

jobs:
  unit-test:
    executor:
      name: android/android-docker
      tag: 2022.08.1
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/run-tests:
          test-command: ./gradlew testDebug
      - android/save-gradle-cache
      ...

在模拟器上运行Android测试

我们的android-test 工作是使用新的安卓模拟器能力。该作业使用与build 作业相同的Android机器执行器。所有后续的作业都将使用相同的执行器,而且缓存恢复步骤也是一样的。

仿真器要求我们在机器镜像上运行,所以我们指定android/android-machine 作为执行器。为了提高构建时间,我们指定resource-class 为xlarge。你也可以在large 执行器上运行,但我发现,资源的缺乏会导致构建时间变慢。我建议你自己做一些实验,为你的项目找到合适的执行器大小。

下一步就更短了;我们只需要android/start-emulator-and-run-tests 命令。这个命令做的和它说的一样:它启动指定的仿真器并运行测试。我们再次传入test-command 作为参数(它使用android/run-tests 命令)。system-image 是完全合格的安卓模拟器系统镜像。我们目前将我们的安卓机器镜像捆绑到SDK的23版。你可以通过使用sdkmanager 工具来安装更多。

jobs:
  android-test:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: system-images;android-30;google_apis;x86
      ...
复制代码

你可以使用Android orb中指定的命令手动编码所有的模拟器设置步骤。create-avd,start-emulator,wait-for-emulator, 和run-tests

储存测试结果

测试不仅仅是运行测试,因为我们可以从CircleCI仪表板内看到确切的失败内容,从而获得很多价值。为此,我们可以使用CircleCI平台内置的store_test_results 功能,这将显示我们通过(或失败)的构建。

单元测试和仪器测试的步骤略有不同,因为它们各自的Gradle任务将测试存储在不同的地方。 对于单元测试,测试将在app/build/test-results ,而对于仪器测试,它们将在app/build/outputs/androidTest-results

我们建议你创建一个新的Save test results 步骤,它使用find 工具并将所有测试结果文件复制到一个共同的位置 - 像test-results/junit ,然后从那里存储。还要确保在步骤中添加when: always 参数,这样无论上面的测试是成功还是失败,步骤都会运行。

存储单元测试

对于这个项目,我们想要基于XML的结果。调用store_artifacts ,使这些结果可以在CircleCI平台上解析。如果你愿意,你也可以存储HTML输出。

...
- android/run-tests:
  test-command: ./gradlew testDebug
- run:
    name: Save test results
    command: |
        mkdir -p ~/test-results/junit/
        find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \;
    when: always
- store_test_results:
    path: ~/test-results
- store_artifacts:
    path: ~/test-results/junit

存储仪表测试

对于不同的测试类型,有不同的regex命令。其余存储测试结果的步骤与单元测试的例子相同。

- android/start-emulator-and-run-tests:
        test-command: ./gradlew connectedDebugAndroidTest
        system-image: << parameters.system-image >>
    - run:
        name: Save test results
        command: |
          mkdir -p ~/test-results/junit/
          find . -type f -regex ".*/build/outputs/androidTest-results/.*xml" -exec cp {} ~/test-results/junit/ \;
        when: always
    - store_test_results:
        path: ~/test-results
    - store_artifacts:
        path: ~/test-results/junit

进行发布构建并存储工件

对于这个项目,我们需要存储我们正在构建的应用程序。为了完成这部分工作,我们使用release-build 工作,它有两部分注意事项。一个run 步骤调用./gradlew assembleRelease 。然后,store_artifacts 指向构建的APK的位置。对于一个完整的CI/CD项目,你可以签署并上传该APK到一个测试版发布服务。你甚至可以用Fastlane在Play Store上创建一个版本。不过这两项活动都不在本文的讨论范围内。

jobs:
  ...
  release-build:
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/restore-gradle-cache
      - android/restore-build-cache
      - run:
          name: Assemble release build
          command: |
            ./gradlew assembleRelease
      - store_artifacts:
          path: app/build/outputs/apk/release

同时在多个Android版本上运行测试

安卓平台一直在发展,每个版本都有自己的错误和特殊性。我相信你已经遇到了其中的一些。避免不同版本的Android出现未定义行为的一个好方法是在尽可能多的版本上进行测试。CircleCI的作业矩阵功能使其更容易在多个版本上运行作业。

要使用作业矩阵,在你想在多个版本上测试的作业中添加一个参数。我们的android-test 作业使用了system-image 参数。我们还为该参数指定了一个默认值,所以你可以不使用该参数来运行作业。

要使用该参数,请将它放在成对的角括号内,指定你需要它的值 -<< >>

 android-test:
    parameters: # this is a parameter
      system-image:
        type: string
        default: system-images;android-30;google_apis;x86
    executor:
      name: android/android-machine
      resource-class: xlarge
    steps:
      - checkout
      - android/start-emulator-and-run-tests:
          test-command: ./gradlew connectedDebugAndroidTest
          system-image: << parameters.system-image >> # parameter being used

要在工作流程中向参数传递数值,请使用我们正在调用的作业的matrix 参数。

workflows:
  jobs:
  ...
  - android-test:
      matrix:
        alias: android-test-all
        parameters:
          system-image:
            - system-images;android-30;google_apis;x86
            - system-images;android-29;google_apis;x86
            - system-images;android-28;google_apis;x86
            - system-images;android-27;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
            - system-images;android-26;google_apis;x86
      name: android-test-<<matrix.system-image>>

通过缓存使每次运行更快

在构建我们的应用程序、安装依赖关系和运行测试的同时,我们往往也想利用缓存。这使得我们可以更少地进行重复的任务。

这可以让你跳过应用程序的预构建部分。

选择什么时候运行

在开发过程中,我们会创建许多提交,应该鼓励开发者尽可能多地将它们推送到远程仓库。不过每一个提交都会创建一个新的CI/CD构建,我们可能不需要在所有可能的设备上运行每一个测试,而只需要一个提交。另一方面,如果main 分支是我们的主要真相来源,我们希望在它上面运行尽可能多的测试,以确保它总是按计划工作。我们可能想只在main ,而不是其他任何分支上启动发布部署。

我们可以使用CircleCI的requiresfilters 段来定义适合我们流程的工作流程。我们的release-build 工作requiresunit-test 工作。这意味着release-build 被放在队列中,直到所有的unit-test 工作都通过。然后才运行release-build 作业。

我们可以使用filters 来设置逻辑规则,只在特定的分支或git标签上运行一个特定的作业。例如,我们为我们的main 分支设置了一个过滤器,用一个完整的仿真器矩阵运行android-test 。在另一个例子中,release 构建只在main 上触发,并且只在其两个必要的作业成功运行后触发。

workflows:
  test-and-build:
    jobs:
      - unit-test
      - android-test: # Commits to any branch - skip matrix of devices
          filters:
            branches:
              ignore: main
      - android-test: # Commits to main branch only - run full matrix
          matrix:
            alias: android-test-all
            parameters:
              system-image:
                - system-images;android-30;google_apis;x86
                - system-images;android-29;google_apis;x86
                - system-images;android-28;google_apis;x86
                - system-images;android-27;google_apis;x86
                - system-images;android-26;google_apis;x86
                - system-images;android-25;google_apis;x86
                - system-images;android-24;google_apis;x86
                - system-images;android-23;google_apis;x86
          name: android-test-<<matrix.system-image>>
          filters:
            branches:
              only: main 
      - release-build:
          requires:
            - unit-test
            - android-test-all
          filters:
            branches:
              only: main # Commits to main branch

改进你的Android应用CI流程

我在本文中描述的步骤仅仅是一个开始。你可以用很多方法来改进这个流程。以下是一些想法。

构建一次,运行多次

如果你是一个有经验的Android开发者,你知道connectedAndroidTest ,总是会从一开始就运行整个构建过程。使用CircleCI,我们可以一次性构建整个应用程序和测试,将工件传递给下面的工作,并简单地在模拟器上运行测试。这个过程有可能为每个作业的运行节省数分钟的构建时间。

为了实现这一点,在每个模拟器的运行中添加三个命令行步骤:安装应用程序,安装仪表应用程序,并运行仪表测试。对于我们的应用程序,我们会输入。

adb install app/build/outputs/apk/debug/app-debug.apk  
adb install app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk  
adb shell am instrument -w com.example.android.architecture.blueprints.reactive.test/androidx.test.runner.AndroidJUnitRunner 

的确,这段代码会将输出发送到命令行。这并不是Gradle提供的整洁的测试输出。

在真实设备上运行

仿真器是一种工具,但真实世界的设备往往(可悲的是)大不相同。没有什么可以取代真实世界的设备测试。为此,你可以使用在云端提供真实设备的解决方案,如Sauce Labs或Firebase Test Lab。

部署给beta测试者,并发布应用程序

这个构建和测试示例项目只是更广泛的CI/CD故事的一个部分。你可以把你的CI/CD过程推进得更远。例如,你可以自动发布你的应用程序的新版本到Play Store。CircleCI有一些指南可以帮助你。

总结

在这篇文章中,我描述了用CircleCI构建一个Android应用程序,并对其进行测试。对于我们的示例项目,我们使用单元测试和Android平台模拟器的矩阵,使用新的CircleCI Android orb和机器图像。我们还谈到了如何存储和显示测试结果,以及如何协调工作流来运行测试不会给我们提供Gradle提供的整齐的测试输出,所有的输出都在一部分设备上。

来自:https://juejin.cn/post/7159158099553550349

上一篇 下一篇

猜你喜欢

热点阅读