提升 Phoenix 中的測試和持續整合
Aaron Renner 於 2021 年 1 月 15 日星期五發布
提升 Phoenix 中的測試和持續整合 持續整合 (CI) 是非常有效率的技術。大型的開放原始碼專案需要使用一組單元測試、一些整合測試,以及自動執行這些測試的程序。不過,CI 也有其難處。組建失敗、複雜的設定,以及緩慢的疊代週期可能會讓人不想等待他們的 PR 被組建。本教學課程將說明我們如何為 Phoenix 專案進行測試和 CI,以及近期變更如何讓這項流程順暢許多。
Phoenix 的測試組
Phoenix 有 4 個不同的測試組,每個組別有不同的用途和相依性。
測試組 | 用途 |
---|---|
主測試<br>位置:/test<br>相依性:<br> - Elixir | 用於 phoenix 架構的核心測試組。測試端點、頻道、路由器、控制器等項目。 |
安裝程式測試<br>位置:/installer/test<br>相依性:<br> - Elixir | 針對 mix phx.new 產生器的測試組。這些測試可確保程式碼產生器在正確的位置寫入正確的程式碼。 |
整合測試<br>位置:/integration_test<br>相依性:<br> - Elixir<br> - PostgresSQL<br> - MySQL<br> - MSSQL | 從頭到尾測試 phoenix 程式碼產生體驗。這些測試會使用 mix phx.new 建立新的專案、執行一個或多個 mix phx.gen.* 指令,並確保沒有編譯警告,程式碼格式設定正確,而且產生的測試組通過。 |
JavaScript 測試 | 測試 Sockets、Channels 和 Presence 的 phoenix JavaScript 程式碼。 |
我們的本地測試方式
能夠下載專案並輕鬆地在本地執行其測試組,是歡迎社群貢獻的關鍵。Phoenix 使用隨 Elixir 一起提供的 ExUnit,因此執行主測試組非常簡單
> mix test
....
Finished in 24.8 seconds
11 doctests, 737 tests, 0 failures
安裝程式測試組一樣很容易執行,只要在 /installer
資料夾中執行 mix test
不過,在我們開始對程式碼產生器進行變更時,所有事情將開始變得更為複雜。雖然我們可以確保我們的產生器在正確的位置建立檔案,但我們不會在實際嘗試執行時才得知所產生的程式碼。因此,Phoenix 在 /integration_test 中有一個整合測試組
> tree
├── config
│ └── config.exs
├── docker-compose.yml
├── mix.exs
├── mix.lock
└── test
├── code_generation
│ ├── app_with_defaults_test.exs
│ ├── app_with_mssql_adapter_test.exs
│ ├── app_with_mysql_adapter_test.exs
│ ├── app_with_no_options_test.exs
│ └── umbrella_app_with_defaults_test.exs
├── support
│ └── code_generator_case.ex
└── test_helper.exs
如果要完整執行這些測試,我們需要存取三個不同的資料庫:Postgres、MySQL 和 MSSQL。這在一般狀況下會很困難,但幸好 Phoenix 有 docker-compose 檔案
version: '3'
services:
postgres:
image: postgres
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgres
mysql:
image: mysql
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
ACCEPT_EULA: Y
SA_PASSWORD: some!Password
ports:
- "1433:1433"
這讓我們得以啟動這些資料庫並執行整合測試,這樣的執行方式如下:
> docker-compose up -d
Starting integration_test_postgres_1 ... done
Starting integration_test_mssql_1 ... done
Starting integration_test_mysql_1 ... done
> mix test --include database
Finished in 230.6 seconds
32 tests, 0 failures
Randomized with seed 896813
我們在持續整合中測試的方式
Phoenix 專案使用 GitHub Actions (GHA) 來執行各組測試套件。與大多數的持續整合服務一樣,GitHub 仰賴其專屬組態檔來安裝建置相依項,啟動外部服務,並執行各種測試套件。
由於 Phoenix 的持續整合和在地測試環境使用不同的工具,導致同組測試出現重複組態和不同環境的狀況。重複組態在不同步時會造成問題。不同的環境表示,我們無法完全確定測試在 GitHub 上執行時,其行為是否會與我們的在地機器相同。且當程式在在地端執行正常,但在持續整合中異常時,必須花費非常久的時間將測試提交推送到 GitHub,並加入除錯敘述,才能找出問題原因。
如果我可以在我的在地機器上執行持續整合建置呢?
這段時間,José 正在和 Vlad 討論在地開發和持續整合之間的差異。Vlad 提議我們試試 Earthly,這是他建立的一個用於指定建置的開源格式。
Vlad 的見解在於,如果我們能夠以一種任何地方都能執行的格式,定義整個建置程序、單元測試、整合測試、服務設定等等,那麼就能輕鬆重現建置失敗的問題。
按照此方法,我們的建置會大致如下:
Earthfile
FROM hexpm/elixir:1.11-erlang-21.0-alpine-3.12.0
all:
BUILD +test
BUILD +integration-test
test:
WORKDIR /src/
COPY . .
RUN mix test
integration-test:
WORKDIR /src/integration_test
COPY . .
RUN mix deps.get
WITH DOCKER --compose docker-compose.yml
RUN mix test --include database
END
我們隨後即可透過呼叫 earthly 並指定一個目標,在在地端或 GHA 中執行各種建置目標,例如全部、測試或整合測試。
> earthly -P +all
現在,如果在 GHA 執行中整合測試失敗,我們有信心能透過執行相同指令,在在地端重現問題。整個建置都已容器化,讓重現問題的過程容易許多。這種方式不僅有助於重現建置失敗,也能讓建置程序本身更容易進行作業,無須推送並等到 GHA 執行才能作業。Earthly 甚至讓我們得以中斷建置管線中的殼層,探究並診斷問題(稍後會詳細說明)。Earthfile 語法建立在 Docker 的層級上,因此我們的 mix.lock
檔未變更時,會使用快取層級,而非再次嘗試下載我們的相依項。在疫情後的時代,當我們重拾旅行時,甚至可以於機上作業建置管線。
測試多個相依項版本
然而,Phoenix 的建置管線比一次執行各個測試套件更複雜。Phoenix 的每次發行不僅必須與最新版本的 Elixir 相容,還必須相容所有支援的版本。OTP 亦同。GHA 透過名為「矩陣策略」的功能,極佳地支援此種使用案例。您可以定義一個矩陣參數,它會使用每個參數來執行您的工作。
matrix:
include:
- elixir: 1.9.4
otp: 20.3.8.26
- elixir: 1.10.4
otp: 21.3.8.17
- elixir: 1.10.4
otp: 23.0.3
矩陣策略會平行執行所有這些工作,表示測試多個版本不會影響我們的建置執行時間。對 Phoenix 這樣的函式庫而言,這項重點功能非常重要。然而,這樣的做法讓本地重現問題更為棘手。如果我們正在執行最新受支援的 OTP 版本,而且 PR 發生錯誤,這時可以使用其中一個支援較舊的 OTP 版本,我們可以嘗試切換版本或僅依賴 GHA 建置來測試變更。實際上,我們有時候只得仰賴 GHA,表示現在的回饋循環變長了。當然這不是世界末日,但話說回來,這又為貢獻流程增添了一點麻煩。
在本地重現矩陣測試
再說一次,使用 earthly 可以讓此情況更容易處理。我們可以加入 Elixir 和 OTP 版本的引數,然後使用這些引數執行任何版本的測試。無需變更本地環境或額外工具。
Phoenix 框架最後使用的解決方案看起來像這樣
setup:
ARG ELIXIR=1.11
ARG OTP=23.0.0
FROM hexpm/elixir:$ELIXIR-erlang-$OTP-alpine-3.12.0
...
integration-test:
FROM +setup
COPY --dir assets config installer lib integration_test priv test ./
WORKDIR /src/integration_test
RUN mix deps.get
WITH DOCKER --compose docker-compose.yml
RUN mix test --include database
END
這樣一來,就可以很輕鬆地在本地測試任何版本組合
> earthly -P --build-arg ELIXIR=1.11.0 --build-arg OTP=23.1.1 +integration-test
+integration-test | Including tags: [:database]
...
+integration-test | Finished in 210.5 seconds
+integration-test | 32 tests, 0 failures
+integration-test | Randomized with seed 330691
output | --> exporting outputs
=========================== SUCCESS ===========================
Earthly 的解決方案低摩擦力,這真的很酷。此外,Earthly 還有一個「互動式」標記,當建置步驟傳回非零狀態時,會彈出一個殼牌。
採用 Earthly
綜上所述,我很興奮地宣布 Phoenix 專案目前的 CI 作業管道是由 Earthly 進行推動。
亞當·高登·貝爾 於 10 月初提交了 PR,在評估過程中,他樂意協助解答我們的問題。 最後的版本 比本文中的範例更為複雜,還會持續演進。目前仍屬初期階段,但對我的工作流程而言,這是一個莫大的勝利。這不會取代我在進行 TDD 循環時的本地測試,但我已經養成一個習慣,在將 PR 推送出去之前,先在本地執行 Earthly 建置,以便減少我在 GitHub Actions 中建置失敗的次數。
我個人認為 Earthly 是新一代 CI 工具的開端,有助於縮小本地建置和 CI 建置之間的差距。如果你時間充裕,我強烈建議你親身體驗並分享你的想法。