第27章 持续集成:GitHub Actions配置、自动化测试与构建发布

持续集成这件事,说白了就是让机器替你跑那些重复的活儿。我早年做Android开发时,每次合并代码都要手动跑测试、打Release包,那叫一个痛苦。后来用了GitHub Actions,整个人都轻松了。

这一章,我们就来聊聊怎么用GitHub Actions搭建一套完整的CI/CD流水线。从自动化测试到构建发布,一步到位。

为什么需要持续集成?

你想想看,一个项目多人协作,每个人都在本地跑测试。有人忘了跑,有人环境不一样,结果就是合并后各种问题。我在项目中遇到过好几次,明明本地测试都过了,一合并就崩。原因很简单——环境不一致,或者有人漏了测试。

持续集成能解决三个核心问题:

  • 自动化测试:每次提交代码,自动跑单元测试、UI测试
  • 构建一致性:所有人在同一个环境里构建,避免「我机器上能跑」的尴尬
  • 发布自动化:打Release包、签名、上传,一键完成

核心原则:能交给机器的,绝不手动。每次提交都触发CI,每次合并都经过验证。

GitHub Actions基础概念

GitHub Actions的工作流由几个核心概念组成:

概念 说明
Workflow 一个完整的自动化流程,定义在 .github/workflows/ 目录下
Job 工作流中的一个任务单元,可以并行或串行执行
Step Job中的具体步骤,比如运行命令、执行脚本
Action 可复用的功能模块,比如 checkout、setup-java
Runner 执行工作流的服务器,GitHub提供Ubuntu/Windows/macOS

我个人习惯把Workflow文件命名为 ci.ymlbuild.yml,放在 .github/workflows/ 目录下。这样一眼就能看出是CI配置。

编写第一个CI工作流

我们先写一个基础的CI工作流,包含代码检查、单元测试和构建APK。

# .github/workflows/ci.yml
name: Android CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle dependencies
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Run lint checks
        run: ./gradlew lint

      - name: Run unit tests
        run: ./gradlew test

      - name: Build debug APK
        run: ./gradlew assembleDebug

      - name: Upload APK artifact
        uses: actions/upload-artifact@v4
        with:
          name: app-debug
          path: app/build/outputs/apk/debug/app-debug.apk

这段配置做了几件事:

  • 监听 main 和 develop 分支的推送和PR
  • 设置JDK 17环境(Android Gradle Plugin 8.x需要)
  • 缓存Gradle依赖,加速后续构建
  • 运行lint检查、单元测试、构建Debug包
  • 把APK上传为构建产物

小技巧:缓存Gradle依赖能节省大量时间。我第一次配置时没加缓存,每次构建都要下载依赖,足足等了5分钟。加了缓存后,第二次构建只要40秒。

自动化测试:单元测试与覆盖率

测试是CI的核心。我建议至少包含单元测试和覆盖率报告。来看看怎么配置:

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Run unit tests with coverage
        run: ./gradlew testDebugUnitTest jacocoTestReport

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: app/build/reports/jacoco/

      - name: Check test results
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: test-failures
          path: app/build/reports/tests/

这里有个细节:if: failure() 这个条件判断。当测试失败时,自动上传测试报告。我曾经遇到过测试失败但不知道具体哪里出问题,有了这个就能直接下载报告查看。

注意:Jacoco覆盖率报告需要先在 build.gradle 中配置。别忘了在模块级build.gradle里加上 jacoco 插件和对应的任务配置。

构建发布:自动打Release包

发布流程比CI复杂一些。需要签名、版本号管理、上传到分发平台。我一般这样配置:

jobs:
  release:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Decode keystore
        env:
          KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
        run: echo "$KEYSTORE_BASE64" | base64 -d > keystore.jks

      - name: Build release APK
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
        run: ./gradlew assembleRelease

      - name: Upload to GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          files: app/build/outputs/apk/release/app-release.apk
          tag_name: v${{ github.run_number }}
          name: Release v${{ github.run_number }}

签名信息存在GitHub Secrets里,千万别硬编码在代码中。我见过有人把keystore密码写在gradle文件里,结果代码开源后被人恶意打包——嗯,那场面挺尴尬的。

SVG:CI/CD流水线架构图

GitHub Actions CI/CD 流水线 代码推送/PR push / pull_request 代码检查 Lint / Detekt 自动化测试 单元测试 / 覆盖率 构建APK assembleDebug/Release 签名打包 Keystore签名 发布上传 GitHub Release 分支策略 main 分支:完整CI + 自动发布Release develop 分支:CI检查 + 构建Debug包 feature 分支:仅运行单元测试

分支策略与条件触发

不同分支应该执行不同的CI流程。我一般这样设计:

  • main分支:完整CI + 自动发布Release包
  • develop分支:CI检查 + 构建Debug包
  • feature分支:仅运行单元测试

用条件判断来实现:

jobs:
  test:
    if: github.ref != 'refs/heads/main'
    # 只跑测试,不构建

  build:
    if: github.ref == 'refs/heads/develop'
    # 构建Debug包

  release:
    if: github.ref == 'refs/heads/main'
    # 构建并发布Release

这样做的好处是,feature分支的PR只需要快速验证,main分支才做完整发布。我曾经见过一个项目,每次PR都跑完整构建,结果等20分钟才出结果——开发效率大打折扣。

避坑指南

配置GitHub Actions时,有几个坑我踩过:

坑1:Gradle守护进程内存不足

默认Runner只有7GB内存,Gradle守护进程可能被OOM杀掉。解决方案是在gradle.properties里设置:org.gradle.jvmargs=-Xmx2048m

坑2:Secrets泄露

千万别在日志里打印Secrets。GitHub会自动屏蔽,但最好还是小心。我曾经不小心把keystore密码打印到日志里,虽然被屏蔽了,但吓得我赶紧换了密码。

坑3:时间超时

免费版Runner每个Job最多6小时。如果项目大,可以考虑分拆Job并行执行。或者用自托管Runner。

总结

GitHub Actions配置其实不难,核心就是三步:写YAML、配Secrets、看结果。我建议从简单的CI开始,逐步加入测试覆盖率、自动发布等功能。

记住一个原则:每次提交都经过验证,每次发布都自动化。这样团队才能高效协作,不会出现「我机器上能跑」的尴尬局面。

我的习惯:每次新建项目,第一件事就是配CI。哪怕项目只有一个人写,也要配。因为——你永远不知道明天会不会有同事加入,或者你自己会不会忘记跑测试。

公众号:蓝海资料掘金营,微信deep3321