iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Mobile Development

我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅系列 第 23

Day 23 - GitHub Actions CI 實作:你的第一個自動化工作流

  • 分享至 

  • xImage
  •  

大家好,歡迎來到第二十三天!在 Day 22,我們完成了 AI Agent 的實作。今天,我們要建立你的第一個 CI (持續整合) 工作流,讓 Crew Up 專案能夠實現現代化的程式碼品質保證。

從專案開發的經驗來看,CI 是現代軟體開發的基石。今天我們將專注於 CI 的核心概念:程式碼分析、自動化測試、建構驗證,並建立一個穩固的 CI 基礎,為後續的 CD (持續部署) 做好準備。

從手動到自動化的好處

  • 早期發現問題:每次程式碼提交都自動檢查
  • 提升開發效率:不用手動執行測試和分析
  • 確保程式碼品質:統一的檢查標準
  • 團隊合作順暢:減少因程式碼問題導致的衝突

今天的目標:建立一個包含「程式碼分析、自動化測試、建構驗證」的 CI 工作流,專注於程式碼品質保證。

學習成果:今天結束時,你會了解 CI 的核心概念,並有一個能實際運作的 CI 工作流,每次推送程式碼時都會自動執行品質檢查。

📝 學習重點:今天專注於 CI 基礎建設,CD 部署相關內容將在 Day 24-25 詳細介紹。

🏗️ CI 的三大核心支柱

CI 工作流建立在三個核心支柱之上,每個支柱都有其特定的作用:

1. 程式碼分析 (flutter analyze) - 品質的第一道防線

作用:檢查程式碼的語法錯誤、類型錯誤、未使用的變數等問題。

為什麼重要

  • 早期發現程式碼問題,避免問題累積
  • 確保程式碼符合 Dart 最佳實務
  • 提升程式碼可讀性和維護性

實作flutter analyze 指令會檢查所有 Dart 檔案,如果發現任何問題,CI 會立即失敗。

2. 自動化測試 (flutter test) - 功能的第二道防線

作用:執行所有單元測試和 Widget 測試,確保業務邏輯正確性。

為什麼重要

  • 確保新功能不破壞現有功能
  • 驗證 Use Case 和 Repository 的正確性
  • 提供程式碼信心的保證

實作flutter test 指令會執行所有測試,確保業務邏輯正確性。

3. 建構驗證 (flutter build apk --flavor development --debug) - 整合的第三道防線

作用:驗證專案可以成功編譯成完整的應用程式,並與專案的 Flavors 設定整合。

為什麼重要

  • 確保程式碼可以編譯
  • 提早發現依賴衝突或資源檔案問題
  • 驗證 CI 環境設定正確
  • 確保 Flavor 配置正確運作

實作flutter build apk --flavor development --debug 指令會建構 development flavor 的 APK,目的僅是驗證建構過程,而非產出發布版本。

📝 注意:這裡使用 debug build,不使用 --release 旗標。關於如何建構已簽名的 release 版本,將在 Day 24 詳細介紹。

🛠️ 實作:建立 ci-cd.yml

現在我們來建立你的第一個 CI 工作流檔案。這個檔案會放在 .github/workflows/ci-cd.yml,每次推送程式碼時都會自動執行。

精華版 CI 工作流(重點片段)

# .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/

關鍵步驟解析

1. 觸發條件 (on:)

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
  • Push 觸發:當你推送程式碼到 maindevelop 分支時執行
  • PR 觸發:當你建立 Pull Request 到 maindevelop 分支時執行

2. 環境設定

jobs:
  quality-and-tests:
    runs-on: ubuntu-latest
  integration-tests:
    needs: quality-and-tests
    runs-on: ubuntu-latest
  • 使用 Ubuntu 環境執行 CI
  • integration-tests 依賴 quality-and-tests 完成後執行

3. 標準 Actions

  • actions/checkout@v5:取得你的程式碼
  • subosito/flutter-action@v2.21.0:設定 Flutter SDK

4. 用快取加速(可選)

- uses: actions/cache@v4
  with:
    path: |
      ~/.pub-cache
      **/.packages
      **/.pub-cache
    key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
  • pubspec.lock 內容當作快取鍵來源

5. Flutter 版本管理最佳實踐

使用 .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

好處

  • 確保本機開發環境與 CI 環境的版本完全一致
  • 版本變更時只需修改一個檔案
  • 避免硬編碼版本號在 YAML 中

6. CI 三大核心支柱

# 第一道防線:程式碼分析
- 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

如何測試你的 CI

  1. 建立檔案:在專案根目錄建立 .github/workflows/ci-cd.yml
  2. 推送程式碼:將檔案推送到 GitHub
  3. 查看結果:在 GitHub 的 Actions 頁面查看執行結果
  4. 修改測試:故意在程式碼中加入錯誤,看看 CI 是否會失敗

📝 提示:第一次執行會比較慢(需要下載依賴),之後的快取會讓執行速度大幅提升。

🚀 進階技巧:程式碼品質提升

覆蓋率報告上傳到 Codecov

除了基本的測試覆蓋率檢查,我們還可以將覆蓋率報告上傳到專業的服務,如 CodecovCoveralls,這能提供:

  • 視覺化報告:在 PR 中顯示覆蓋率變化
  • 歷史追蹤:追蹤覆蓋率趨勢
  • 團隊協作:統一的品質標準

實作步驟

  1. 註冊 Codecov 帳號:前往 codecov.io 並連接你的 GitHub 專案
  2. 取得 Token:在 Codecov 專案設定中取得上傳 Token
  3. 設定 GitHub Secret:將 Token 存入 CODECOV_TOKEN Secret
  4. 在 CI 中加入上傳步驟
# 上傳覆蓋率報告到 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

好處

  • 在 PR 中自動顯示覆蓋率變化
  • 提供詳細的覆蓋率報告
  • 設定覆蓋率門檻,確保程式碼品質

✨ 加入 Slack 即時通知

我們的 CI 工作流已經可以自動檢查程式碼了,但如果每次都需要手動去 GitHub Actions 頁面查看結果,還是有些不方便。如果能讓 CI 流程在執行完畢後,自動在我們的 Slack 頻道發送通知,那該有多好!

這一步是可選的,但它能極大地提升團隊的開發體驗。

實作三步驟(可選)

我們將使用 Slack 官方的 GitHub Action (slackapi/slack-github-action) 來簡化這個過程。

步驟 1:取得 Slack Webhook URL

  1. 前往 Slack Webhook 設定頁面
  2. 在你的 Slack 工作區,建立一個新的 App 或選擇現有的
  3. 啟用 Incoming Webhooks 功能
  4. 選擇你想要接收通知的頻道(例如 #dev-log#ci-status),並產生一個 Webhook URL
  5. 複製這個 URL,它看起來像 https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

步驟 2:將 Webhook URL 存入 GitHub Secrets

Webhook URL 屬於敏感資訊,不應該直接寫在 ci-cd.yml 檔案中。我們需要使用 GitHub 的 Secrets 功能來安全地儲存它。

  1. 在你的 GitHub 專案頁面,進入 Settings > Secrets and variables > Actions
  2. 點擊 New repository secret
  3. Name 欄位輸入 SLACK_WEBHOOK_URL
  4. Value 欄位貼上你剛剛複製的 Slack Webhook URL
  5. 點擊 Add secret

步驟 3:在 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 工作流

將這個新步驟加入後,你最終的 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 工作流

  1. 程式碼分析flutter analyze --fatal-infos 確保程式碼品質
  2. 自動化測試flutter test --coverage 確保功能正確性
  3. 建構驗證flutter build apk --flavor development --debug 確保可以成功編譯
  4. 程式碼格式dart format 確保程式碼風格一致
  5. 整合測試flutter test integration_test/ 確保端到端功能
  6. 覆蓋率報告:上傳到 Codecov 提供視覺化報告
  7. Slack 通知:即時接收 CI 執行結果(可選功能)

✅ 學習了 CI 的核心概念

  • CI 的三層防線設計
  • GitHub Actions 的基本用法
  • Flutter 專案的 CI 最佳實務
  • Flutter 版本管理最佳實踐(.flutter-version 檔案)
  • GitHub Secrets 的安全管理
  • 條件式步驟執行 (if: always())
  • 專業覆蓋率報告服務整合

下一步:Day 24 - Android 部署

明天(Day 24),我們將在這個穩固的 CI 基礎上,學習:

  • Android Keystore 管理:如何建立和管理簽名金鑰
  • Google Play Console 設定:如何設定應用程式發布
  • 自動化 APK 上傳:如何將 CI 擴展為 CD
  • 版本管理策略:如何管理應用程式版本

我們將把今天的 ci-cd.yml 擴展為完整的 CI/CD 流程,讓你的應用程式能夠自動部署到 Google Play Store。

期待與您在 Day 24 相見!


📋 相關資源

📝 專案資訊

  • 專案名稱: Crew Up!
  • 開發日誌: Day 23 - 你的第一個 Flutter CI 工作流
  • 文章日期: 2025-10-07
  • 技術棧: GitHub Actions, Flutter 3.13.0, Clean Architecture

上一篇
Day 22 - AI Agent 實作:聊天室氣氛分析與建議系統
下一篇
Day 24 - Android 建置與簽名實作:從 AAB 到自動化部署
系列文
我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言