設定持續整合


為你的軌道設定持續整合(CI)非常重要,因為它能幫助你及早發現錯誤。

GitHub Actions

Exercism 的儲存庫(包含軌道儲存庫)使用 GitHub Actions 來執行 CI。 GitHub Actions 以_工作流_為基礎,工作流定義了在特定事件發生時(例如推送提交)自動執行的腳本。 想進一步了解 GitHub Actions 工作流,請參閱工作流文件。

預先安裝的工作流

軌道預先安裝了許多工作流,其中大部分你_不_應該修改(這些稱為_共用工作流_)。 不過,有一個工作流是你_應該_修改的,那就是test.yml工作流。

測試工作流

test.yml工作流的目標是驗證軌道裡的練習都處於良好狀態。 當你推送提交到main分支或提取要求的分支時,這個工作流會自動執行(在 GitHub Actions 的術語中叫做_觸發_)。

工作流本身不應該做太多事,除了以下幾件事:

  • 取出程式碼(已實作)
  • 安裝相依套件(例如安裝套件,選用)
  • 安裝工具(例如安裝 SDK,選用)
  • 執行驗證練習的腳本(已實作)

實作驗證練習的腳本

如前所述,練習是透過腳本來驗證的,也就是bin/verify-exercises(bash)腳本。 這個腳本_幾乎_完成了,它會做以下幾件事:

  • 逐一走訪所有練習目錄
  • 對每個練習目錄,它會接著:
    • 將範例解答複製到(待填的)解答檔案(已實作)
    • 呼叫unskip_tests函式,你可以在其中取消跳過測試檔案裡的測試(選用)
    • 呼叫run_tests函式,你應該在其中執行測試(必填)

run_tests和unskip_tests這兩個函式是你唯一需要實作的部分。

取消跳過測試

如果你的軌道支援跳過測試,我們就必須確保在驗證練習的範例解答時,沒有任何測試被跳過。 一般來說,軌道支援「取消跳過」測試的方式有兩種:

  1. 從測試檔案中移除標註/程式碼/文字。 例如,把test.skip改成test。
  2. 提供環境變數。 例如,設定SKIP_TESTS=false。

從測試檔案中移除標註/程式碼/文字

如果跳過測試是以檔案為基礎(也就是上面提到的第一種做法),請編輯unskip_tests函式來修改測試檔案(現有的程式碼已經會處理測試檔案的走訪)。

Note

The unskip_test function runs on a copy of an exercise directory, so feel free to modify the files as you see fit.

範例

Arturo 軌道的 bin/verify-exercises file使用sed來取消跳過測試檔案中的測試:

unskip_tests() {
    jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
        sed -i 's/test.skip/test/g' "${test_file}"
    done
}

提供環境變數

Caution

If unskipping tests requires an environment variable to be set, make sure that it is set in the run_tests function.

執行測試

run_tests函式負責執行練習的測試。 這個函式被呼叫時,範例解答檔案已經被複製到(待填的)解答檔案了,所以你只需要呼叫正確的指令來執行測試。

如果所有測試都通過,這個函式必須回傳 0 作為結束代碼,否則要回傳非零的結束代碼。

Note

The run_tests function runs on a copy of an exercise directory, so feel free to modify the files as you see fit.

選項 1:使用語言工具

驗證練習腳本的預設做法是使用該語言的工具(SDK/執行檔等),這也是大多數軌道採用的方式。 每個軌道執行測試的方式各不相同,但通常就只是一道指令。

範例

Arturo 軌道的 bin/verify-exercises file修改run_tests函式,直接在測試檔案上呼叫arturo指令:

run_tests() {
    arturo tester.art
}

選項 2:使用測試執行器的 Docker 映像檔

第二種做法是透過執行軌道的測試執行器來驗證練習。 這當然取決於該軌道是否已有可用的測試執行器。

如果你的軌道還沒有測試執行器,你可以選擇:

  • 建置一個可用的測試執行器,或是
  • 採用選項 1,直接使用該語言的工具

預設的bin/verify-exercises腳本需要做以下修改:

  1. 確認docker指令可以使用
  2. 拉取(下載)測試執行器的 Docker 映像檔
  3. 使用docker run在每個練習上執行測試執行器的 Docker 映像檔
  4. 使用jq確認 Docker 容器回傳的results.json檔案顯示所有測試都通過
  5. 移除unskip_test函式以及對該函式的呼叫
Note

The main benefit of this approach is that it best mimics how tests are being run in production (on the website). With this approach, it is less likely that things fail in production that passed in CI. The downside of this approach is that it usually is slower, due to having to pull the Docker image and the overhead of Docker.

範例

Unison 軌道的 bin/verify-exercises file加入了檢查,確認docker指令也已安裝:

required_tool docker

接著,它會拉取該軌道的測試執行器映像檔:

docker pull exercism/unison-test-runner

然後它會修改run_tests函式,使用docker run在目前的練習(位於工作目錄)上執行測試執行器,接著再用jq指令檢查狀態是否正確:

run_tests() {
    local slug

    slug="${1}"

    docker run \
        --rm \
        --network none \
        --mount type=bind,src="${PWD}",dst=/solution \
        --mount type=bind,src="${PWD}",dst=/output \
        --tmpfs /tmp:rw \
        exercism/unison-test-runner "${slug}" "/solution" "/output"
    jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
}

最後,我們需要修改run_tests指令的呼叫方式,因為它現在需要 slug:

run_tests "${slug}"

實作測試工作流

既然verify-exercises腳本已經完成,現在該來完成test.yml工作流了。 該怎麼做,取決於verify-exercises腳本採用了哪一種實作方式。

選項 1:使用語言工具

如果verify-exercises腳本直接使用該語言的工具,測試工作流就需要安裝:

  • 語言工具的相依套件,例如 openssh 或 C/C++ 編譯器。
  • 語言工具本身,例如 SDK 或執行檔。 如果安裝語言工具的過程_沒有_把安裝好的執行檔加入路徑,請務必將它加入 GitHub Actions 的系統路徑。

完成之後,verify-exercises應該就能如預期運作,你也成功設定好 CI 了!

範例請見 Arturo 軌道的test.yml工作流:

name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev libmpfr-dev

      - name: Install Arturo
        run: bin/install-arturo
        env:
          GH_TOKEN: ${{ github.token }}

      - name: Verify all exercises
        run: bin/verify-exercises

選項 2:使用測試執行器的 Docker 映像檔

第二種做法是透過執行軌道的測試執行器來驗證練習。 這種做法需要滿足兩個條件:

  1. 該軌道已有可用的測試執行器
  2. verify-exercises腳本使用測試執行器的 Docker 映像檔來執行練習的測試

如果你的軌道還沒有測試執行器,你可以選擇:

  • 建置一個可用的測試執行器,或是
  • 採用選項 1,直接使用該語言的工具

這種做法有幾個優點:

  1. 你不需要在測試工作流中安裝任何相依套件或工具(因為它們都已經安裝在 Docker 映像檔裡了)
  2. 這種做法最能模擬測試在正式環境(網站上)執行的方式,降低正式環境出問題的可能性。

主要的缺點是它通常比較慢,因為必須拉取 Docker 映像檔,還得負擔 Docker 的額外開銷。

拉取測試執行器 Docker 映像檔的方式有幾種:

  1. 在verify-exercises檔案中下載映像檔。 Unison 軌道採用這種做法。
  2. 在工作流中下載映像檔。 Standard ML 軌道採用這種做法。
  3. 在工作流中建置映像檔。 8th 軌道採用這種做法。

那麼該用哪一種做法呢? 我們建議_至少_採用第 1 種做法,讓verify-exercises腳本能夠_獨立運作_。 如果你的映像檔特別大,不妨也採用第 3 種做法,它會把建置好的 Docker 映像檔存進 GitHub Actions 的快取。 之後的執行就可以直接從快取讀取 Docker 映像檔,不必重新下載,效能上可能更好(請實際測量確認)。

選項 3:在測試執行器的 Docker 映像檔內執行驗證練習腳本

第三種替代做法是前兩種做法的混合。 這裡同樣會用到測試執行器的 Docker 映像檔,只是這次我們是在_那個 Docker 映像檔裡面_執行verify-exercises腳本。 要採用這種做法,我們需要把工作流的容器設為測試執行器:

container:
  image: exercism/vimscript-test-runner

這樣一來,我們就可以跳過安裝相依套件和工具的步驟(因為它們都已經安裝在測試執行器的 Docker 映像檔裡了),直接執行bin/verify-exercises腳本。

範例

vimscript 軌道的test.yml工作流採用這種做法:

name: Verify Exercises

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-24.04
    container:
      image: exercism/vimscript-test-runner

    steps:
      - name: Checkout repository
        uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332

      - name: Verify all exercises
        run: bin/verify-exercises