
现代应用开发离不开自动化测试与部署流水线。手动测试和部署费时、容易出错,而且随着团队扩大难以为继。
本指南将演示如何使用 GitHub Actions 为 Flutter 应用搭建稳健的 CI/CD 流水线。你将学会自动化测试、代码质量检查、构建,以及部署到 Google Play Store 和 Apple App Store。
GITHUB ACTIONS 基础
GitHub Actions 为公开仓库提供免费 CI/CD,私有仓库也有充裕的免费额度,并能与你的 Flutter 开发流程无缝集成。
# .github/workflows/flutter_ci.yml
name: Flutter CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .
- name: Analyze project source
run: flutter analyze --fatal-infos
- name: Run tests
run: flutter test --coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
build_android:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Build Android APK
run: flutter build apk --release
- name: Build Android App Bundle
run: flutter build appbundle --release
- name: Upload APK artifact
uses: actions/upload-artifact@v3
with:
name: android-apk
path: build/app/outputs/flutter-apk/app-release.apk
- name: Upload AAB artifact
uses: actions/upload-artifact@v3
with:
name: android-aab
path: build/app/outputs/bundle/release/app-release.aab
build_ios:
needs: test
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Build iOS (no signing)
run: flutter build ios --release --no-codesign
- name: Upload iOS artifact
uses: actions/upload-artifact@v3
with:
name: ios-build
path: build/ios/iphoneos/Runner.app工作流优势
- 每次 push 和 PR 均会运行
- 并行构建 Android 和 iOS
- 自动化代码质量检查
- 覆盖率报告
主要特性
- Flutter 版本缓存
- 依赖缓存
- 构建产物上传
- 多平台支持
全面的测试流水线
稳健的测试流水线涵盖单元测试、Widget 测试、集成测试及代码质量检查,确保应用在部署前稳定可靠。
# .github/workflows/comprehensive_testing.yml
name: Comprehensive Testing
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
quality_checks:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
# Code formatting check
- name: Check code formatting
run: dart format --output=none --set-exit-if-changed .
# Static analysis
- name: Run static analysis
run: flutter analyze --fatal-infos --fatal-warnings
# Import sorting check
- name: Check import sorting
run: |
flutter pub global activate import_sorter
import_sorter --check-only
# Unused dependencies check
- name: Check for unused dependencies
run: |
flutter pub global activate dependency_validator
dependency_validator
# Security audit
- name: Security audit
run: |
flutter pub deps --json > deps.json
# Add security scanning tool here
unit_tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Run unit tests
run: |
flutter test \
--coverage \
--test-randomize-ordering-seed random \
--reporter expanded
- name: Generate coverage report
run: |
sudo apt-get update
sudo apt-get install lcov
genhtml coverage/lcov.info -o coverage/html
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
verbose: true
widget_tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Run widget tests
run: flutter test test/widget_test/
integration_tests:
runs-on: macos-latest
strategy:
matrix:
device:
- "iPhone 14 Pro Simulator (16.0)"
- "iPad Pro (11-inch) (4th generation) Simulator (16.0)"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
- name: Start iOS Simulator
run: |
xcrun simctl list devices
xcrun simctl boot "${{ matrix.device }}" || true
- name: Run integration tests
run: |
flutter test integration_test/ \
--device-id="${{ matrix.device }}"
- name: Upload test results
uses: actions/upload-artifact@v3
if: failure()
with:
name: integration-test-results
path: test_results/测试策略:
- 单元测试覆盖业务逻辑(占测试 70%)
- Widget 测试覆盖 UI 组件(占测试 20%)
- 集成测试覆盖用户流程(占测试 10%)
- 强制执行代码覆盖率阈值
自动化部署流水线
测试通过后,自动将 Flutter 应用部署到 Google Play Store 和 Apple App Store,涵盖应用签名、版本管理及发布说明。
# .github/workflows/deploy.yml
name: Deploy to App Stores
on:
push:
tags:
- 'v*' # Deploy when version tags are pushed
jobs:
deploy_android:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
# Setup Android signing
- name: Setup Android signing
run: |
echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app/keystore.jks
echo "storeFile=keystore.jks" >> android/key.properties
echo "keyAlias=$ANDROID_KEY_ALIAS" >> android/key.properties
echo "storePassword=$ANDROID_STORE_PASSWORD" >> android/key.properties
echo "keyPassword=$ANDROID_KEY_PASSWORD" >> android/key.properties
env:
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Build signed App Bundle
run: flutter build appbundle --release
- name: Deploy to Google Play Console
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
packageName: com.onon.yourapp
releaseFiles: build/app/outputs/bundle/release/app-release.aab
track: production
status: completed
inAppUpdatePriority: 2
userFraction: 0.33
whatsNewDirectory: distribution/whatsnew
mappingFile: build/app/outputs/mapping/release/mapping.txt
deploy_ios:
runs-on: macos-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Install dependencies
run: flutter pub get
# Setup iOS certificates and provisioning profiles
- name: Install Apple Certificate
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
p12-password: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
- name: Install Provisioning Profile
uses: apple-actions/download-provisioning-profiles@v1
with:
bundle-id: com.onon.yourapp
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
- name: Build iOS app
run: |
flutter build ios --release --no-codesign
cd ios
xcodebuild -workspace Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-destination generic/platform=iOS \
-archivePath $PWD/build/Runner.xcarchive \
archive
xcodebuild -exportArchive \
-archivePath $PWD/build/Runner.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath $PWD/build
- name: Deploy to TestFlight
uses: apple-actions/upload-testflight-build@v1
with:
app-path: ios/build/Runner.ipa
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-private-key: ${{ secrets.APPSTORE_PRIVATE_KEY }}
create_release:
needs: [deploy_android, deploy_ios]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
🚀 New release deployed to app stores!
## What's New
- Check CHANGELOG.md for detailed changes
## Deployment Status
- ✅ Android: Google Play Store
- ✅ iOS: TestFlight & App Store
draft: false
prerelease: false部署功能:
- 两个平台的自动化应用签名
- 分阶段发布以降低风险
- 从更新日志自动生成发布说明
- 版本管理与标签
环境与密钥管理
妥善的密钥管理和环境配置能确保部署安全,同时支持开发、预发布和生产等不同环境。
# .github/workflows/multi_environment.yml
name: Multi-Environment Deployment
on:
push:
branches:
- develop # Deploy to staging
- main # Deploy to production
pull_request:
branches:
- main # Test on PR
jobs:
determine_environment:
runs-on: ubuntu-latest
outputs:
environment: ${{ steps.env.outputs.environment }}
api_url: ${{ steps.env.outputs.api_url }}
steps:
- name: Determine environment
id: env
run: |
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "environment=production" >> $GITHUB_OUTPUT
echo "api_url=https://api.yourapp.com" >> $GITHUB_OUTPUT
elif [[ "${{ github.ref }}" == "refs/heads/develop" ]]; then
echo "environment=staging" >> $GITHUB_OUTPUT
echo "api_url=https://staging-api.yourapp.com" >> $GITHUB_OUTPUT
else
echo "environment=test" >> $GITHUB_OUTPUT
echo "api_url=https://test-api.yourapp.com" >> $GITHUB_OUTPUT
fi
build_and_test:
needs: determine_environment
runs-on: ubuntu-latest
environment: ${{ needs.determine_environment.outputs.environment }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.24.0'
channel: 'stable'
cache: true
- name: Create environment config
run: |
cat > lib/config/environment.dart << EOF
class Environment {
static const String apiUrl = '${{ needs.determine_environment.outputs.api_url }}';
static const String environment = '${{ needs.determine_environment.outputs.environment }}';
static const String apiKey = '${{ secrets.API_KEY }}';
static const bool isProduction = ${{ needs.determine_environment.outputs.environment == 'production' }};
}
EOF
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Build for environment
run: |
if [[ "${{ needs.determine_environment.outputs.environment }}" == "production" ]]; then
flutter build apk --release --flavor production -t lib/main_production.dart
elif [[ "${{ needs.determine_environment.outputs.environment }}" == "staging" ]]; then
flutter build apk --release --flavor staging -t lib/main_staging.dart
else
flutter build apk --debug --flavor development -t lib/main_development.dart
fi
# GitHub Repository Secrets Setup:
# ANDROID_KEYSTORE_BASE64 - Base64 encoded Android keystore
# ANDROID_KEY_ALIAS - Android key alias
# ANDROID_STORE_PASSWORD - Android keystore password
# ANDROID_KEY_PASSWORD - Android key password
# IOS_CERTIFICATE_BASE64 - Base64 encoded iOS certificate
# IOS_CERTIFICATE_PASSWORD - iOS certificate password
# APPSTORE_ISSUER_ID - App Store Connect issuer ID
# APPSTORE_KEY_ID - App Store Connect key ID
# APPSTORE_PRIVATE_KEY - App Store Connect private key
# GOOGLE_PLAY_SERVICE_ACCOUNT_JSON - Google Play Console service account
# API_KEY - Your app's API key
# FIREBASE_CONFIG_ANDROID - Firebase config for Android
# FIREBASE_CONFIG_IOS - Firebase config for iOS
# Environment-specific secrets:
# For each environment (production, staging, test), create:
# - API_URL
# - DATABASE_URL
# - ANALYTICS_KEY
# - Feature flags configuration安全最佳实践:
- 所有敏感数据均存放在 GitHub Secrets
- 为每个环境使用独立配置
- 定期轮换密钥
- 仅允许必要的工作流访问密钥
CI/CD 最佳实践
快速反馈循环: 将构建时间控制在 10 分钟以内。利用缓存、并行任务和增量构建加快流水线速度。
快速失败: 先运行静态分析和单元测试。若基本质量检查不通过,就不要在昂贵的集成测试上浪费时间。
分阶段部署: 先部署到预发布环境,再部署到生产环境。使用功能开关(feature flags)和灰度发布,将风险降到最低。
全面监控: 跟踪构建成功率、测试覆盖率、部署频率及变更前置时间。
回滚策略: 时刻准备好快速回滚部署的方案。保留旧版本随时可用,并定期测试回滚流程。


