大家好,歡迎來到第二十三天!在 Day 22,我們完成了 AI Agent 的實作。今天,我們要建立你的第一個 CI (持續整合) 工作流,讓 Crew Up 專案能夠實現現代化的程式碼品質保證。
從專案開發的經驗來看,CI 是現代軟體開發的基石。今天我們將專注於 CI 的核心概念:程式碼分析、自動化測試、建構驗證,並建立一個穩固的 CI 基礎,為後續的 CD (持續部署) 做好準備。
從手動到自動化的好處:
今天的目標:建立一個包含「程式碼分析、自動化測試、建構驗證」的 CI 工作流,專注於程式碼品質保證。
學習成果:今天結束時,你會了解 CI 的核心概念,並有一個能實際運作的 CI 工作流,每次推送程式碼時都會自動執行品質檢查。
📝 學習重點:今天專注於 CI 基礎建設,CD 部署相關內容將在 Day 24-25 詳細介紹。
CI 工作流建立在三個核心支柱之上,每個支柱都有其特定的作用:
flutter analyze
) - 品質的第一道防線作用:檢查程式碼的語法錯誤、類型錯誤、未使用的變數等問題。
為什麼重要:
實作:flutter analyze
指令會檢查所有 Dart 檔案,如果發現任何問題,CI 會立即失敗。
flutter test
) - 功能的第二道防線作用:執行所有單元測試和 Widget 測試,確保業務邏輯正確性。
為什麼重要:
實作:flutter test
指令會執行所有測試,確保業務邏輯正確性。
flutter build apk --flavor development --debug
) - 整合的第三道防線作用:驗證專案可以成功編譯成完整的應用程式,並與專案的 Flavors 設定整合。
為什麼重要:
實作:flutter build apk --flavor development --debug
指令會建構 development flavor 的 APK,目的僅是驗證建構過程,而非產出發布版本。
📝 注意:這裡使用 debug build,不使用
--release
旗標。關於如何建構已簽名的 release 版本,將在 Day 24 詳細介紹。
ci-cd.yml
現在我們來建立你的第一個 CI 工作流檔案。這個檔案會放在 .github/workflows/ci-cd.yml
,每次推送程式碼時都會自動執行。
# .github/workflows/ci-cd.yml
name: Complete CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
jobs:
# 程式碼品質檢查與測試
quality-and-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# 讀取 .flutter-version 檔案內容
- name: Get Flutter version
id: flutter-version
run: echo "version=$(cat .flutter-version)" >> $GITHUB_OUTPUT
- uses: subosito/flutter-action@v2.21.0
with:
flutter-version: ${{ steps.flutter-version.outputs.version }}
channel: 'stable'
cache: true
- run: flutter pub get
- run: flutter packages pub run build_runner build --delete-conflicting-outputs
# CI 三大核心支柱
- run: flutter analyze --fatal-infos
- run: flutter test --coverage
- run: dart format --output=none --set-exit-if-changed .
# 第三道防線:建構驗證 (使用 development flavor)
- name: Build validation (Android)
run: flutter build apk --flavor development --debug
# 上傳覆蓋率報告到 Codecov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
# 整合測試
integration-tests:
needs: quality-and-tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# 讀取 .flutter-version 檔案內容
- name: Get Flutter version
id: flutter-version
run: echo "version=$(cat .flutter-version)" >> $GITHUB_OUTPUT
- uses: subosito/flutter-action@v2.21.0
with:
flutter-version: ${{ steps.flutter-version.outputs.version }}
channel: 'stable'
cache: true
- run: flutter pub get
- run: flutter packages pub run build_runner build --delete-conflicting-outputs
- run: flutter test integration_test/
on:
)on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
main
或 develop
分支時執行main
或 develop
分支時執行jobs:
quality-and-tests:
runs-on: ubuntu-latest
integration-tests:
needs: quality-and-tests
runs-on: ubuntu-latest
integration-tests
依賴 quality-and-tests
完成後執行actions/checkout@v5
:取得你的程式碼subosito/flutter-action@v2.21.0
:設定 Flutter SDK- uses: actions/cache@v4
with:
path: |
~/.pub-cache
**/.packages
**/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
pubspec.lock
內容當作快取鍵來源使用 .flutter-version
檔案:
# 讀取 .flutter-version 檔案內容
- name: Get Flutter version
id: flutter-version
run: echo "version=$(cat .flutter-version)" >> $GITHUB_OUTPUT
- uses: subosito/flutter-action@v2.21.0
with:
flutter-version: ${{ steps.flutter-version.outputs.version }}
channel: 'stable'
cache: true
好處:
# 第一道防線:程式碼分析
- run: flutter analyze --fatal-infos
# 第二道防線:自動化測試
- run: flutter test --coverage
# 第三道防線:建構驗證 (使用 development flavor)
- name: Build validation (Android)
run: flutter build apk --flavor development --debug
# 進階:覆蓋率報告上傳
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
.github/workflows/ci-cd.yml
📝 提示:第一次執行會比較慢(需要下載依賴),之後的快取會讓執行速度大幅提升。
除了基本的測試覆蓋率檢查,我們還可以將覆蓋率報告上傳到專業的服務,如 Codecov 或 Coveralls,這能提供:
實作步驟:
CODECOV_TOKEN
Secret# 上傳覆蓋率報告到 Codecov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
好處:
我們的 CI 工作流已經可以自動檢查程式碼了,但如果每次都需要手動去 GitHub Actions 頁面查看結果,還是有些不方便。如果能讓 CI 流程在執行完畢後,自動在我們的 Slack 頻道發送通知,那該有多好!
這一步是可選的,但它能極大地提升團隊的開發體驗。
我們將使用 Slack 官方的 GitHub Action (slackapi/slack-github-action
) 來簡化這個過程。
#dev-log
或 #ci-status
),並產生一個 Webhook URLhttps://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
Webhook URL 屬於敏感資訊,不應該直接寫在 ci-cd.yml
檔案中。我們需要使用 GitHub 的 Secrets 功能來安全地儲存它。
Settings > Secrets and variables > Actions
New repository secret
Name
欄位輸入 SLACK_WEBHOOK_URL
Value
欄位貼上你剛剛複製的 Slack Webhook URLAdd secret
ci-cd.yml
中加入通知 Job最後,在 ci-cd.yml
檔案中加入一個獨立的通知 Job:
# 通知 Job
notify-slack:
name: Notify Slack
runs-on: ubuntu-latest
# 使用 needs 來確保在所有測試和建構工作完成後執行
needs: [quality-and-tests, build-android-multi-env, build-ios-multi-env, integration-tests, security-scan]
# 使用 if: always() 確保無論成功或失敗都會執行
if: always()
steps:
- name: Send Slack Notification
uses: slackapi/slack-github-action@v1.26.0
with:
payload: |
{
"text": "CI/CD for `${{ github.repository }}` on `${{ github.ref_name }}`",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "CI/CD for `${{ github.repository }}` on `${{ github.ref_name }}`"
}
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Status:*\n${{ needs.quality-and-tests.result == 'success' && needs.build-android-multi-env.result == 'success' && needs.build-ios-multi-env.result == 'success' && needs.integration-tests.result == 'success' && needs.security-scan.result == 'success' ? '✅ Success' : '❌ Failure' }}" },
{ "type": "mrkdwn", "text": "*Triggered by:*\n${{ github.actor }}" },
{ "type": "mrkdwn", "text": "*Quality & Tests:*\n${{ needs.quality-and-tests.result }}" },
{ "type": "mrkdwn", "text": "*Android Build:*\n${{ needs.build-android-multi-env.result }}" },
{ "type": "mrkdwn", "text": "*iOS Build:*\n${{ needs.build-ios-multi-env.result }}" },
{ "type": "mrkdwn", "text": "*Integration Tests:*\n${{ needs.integration-tests.result }}" },
{ "type": "mrkdwn", "text": "*Security Scan:*\n${{ needs.security-scan.result }}" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Workflow Run" },
"url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
關鍵設定:
needs: [...]
:確保在所有測試和建構工作完成後執行if: always()
:無論成功或失敗都會執行通知slackapi/slack-github-action@v1.26.0
:使用官方 Slack Action,提供更豐富的通知功能SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
:統一使用 SLACK_WEBHOOK_URL Secret將這個新步驟加入後,你最終的 CI 工作流重點會是這樣:
# .github/workflows/ci-cd.yml(CI 重點摘要)
name: Complete CI/CD Pipeline
on: { push: { branches: [main, develop] }, pull_request: { branches: [main, develop] } }
jobs:
quality-and-tests: { steps: [analyze, test, format] }
integration-tests: { needs: quality-and-tests, steps: [integration test] }
✅ 建立了一個完整的 CI 工作流:
flutter analyze --fatal-infos
確保程式碼品質flutter test --coverage
確保功能正確性flutter build apk --flavor development --debug
確保可以成功編譯dart format
確保程式碼風格一致flutter test integration_test/
確保端到端功能✅ 學習了 CI 的核心概念:
.flutter-version
檔案)if: always()
)明天(Day 24),我們將在這個穩固的 CI 基礎上,學習:
我們將把今天的 ci-cd.yml
擴展為完整的 CI/CD 流程,讓你的應用程式能夠自動部署到 Google Play Store。
期待與您在 Day 24 相見!