第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.yml 或 build.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流水线架构图
分支策略与条件触发
不同分支应该执行不同的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。哪怕项目只有一个人写,也要配。因为——你永远不知道明天会不会有同事加入,或者你自己会不会忘记跑测试。