測試產生器是一款 track 專屬的軟體,用來自動產生練習的測試。它的做法,是把練習的 JSON 測試案例轉換成該 track 程式語言的測試。
擁有測試產生器有以下幾項好處:
一般來說,執行測試產生器的情況有兩種:
為新練習加上測試產生器之後,就能產生它的測試檔案。只要測試產生器本身已經實作完成,替新練習產生測試,會比從零開始自己寫測試輕鬆得多。
練習有了測試產生器之後,你可以重新執行它,把練習更新/同步到最新的標準資料。我們建議定期這麼做,檢查是否有需要更新的問題測試案例,或是有沒有想加入的新測試。
替練習實作測試產生器時,有 2 種可能的起點:
如果已經有現成的測試,實作測試產生器時,要確保它產生的測試不會破壞現有的解法。
大致上來說,測試檔案的產生方式有兩種:
我們發現,以程式碼為主的做法會讓測試產生器的程式碼變得相當複雜,而範本做法則比較簡單。
我們建議採用以下流程:
tests.toml檔案中標記為 include = false的測試案例這個做法的主要好處,在於每個練習都有自己專屬的範本,這麼做可以:
設計測試產生器時,請盡量:
測試產生器通常(大部分)是用該 track 的程式語言寫成的。
你當然可以自由使用其他語言,但每多一種語言,就會讓這個 track 更難維護、更難讓人貢獻。 因此我們建議盡可能使用該 track 的程式語言,因為這樣維護和貢獻都會比較容易。
如果你的 track 有格式化程式碼的工具,可以考慮在渲染範本_之後_,把它當成後處理步驟來執行。
測試產生器處理的核心資料,是練習的 canonical-data.json 檔案。這個檔案定義在 exercism/problem-specifications 儲存庫中,該儲存庫定義了許多 Exercism 練習共用的中繼資料。
不是每個練習都有 canonical-data.json 檔案!
如果沒有的話,你就得手動建立測試,因為沒有資料可以讓測試產生器處理。
標準資料定義在一個 JSON 物件中。這個物件裡有一個 "cases"欄位,裡面放的是測試案例。這些測試案例(通常)會與你 track 裡的測試一一對應。
每個測試案例都有幾個屬性,其中最重要的是描述、屬性、輸入值和預期值。以下是 leap 練習的 canonical-data.json 檔案的(部分)範例:
{
"exercise": "leap",
"cases": [
{
"uuid": "6466b30d-519c-438e-935d-388224ab5223",
"description": "year not divisible by 4 in common year",
"property": "leapYear",
"input": {
"year": 2015
},
"expected": false
},
{
"uuid": "4fe9b84c-8e65-489e-970b-856d60b8b78e",
"description": "year divisible by 4, not divisible by 100 in leap year",
"property": "leapYear",
"input": {
"year": 1996
},
"expected": true
}
]
}
測試產生器的主要職責,就是把這份 JSON 資料轉換成該 track 專屬的測試。以下是上述 JSON 可能轉換成的 Nim 測試程式碼:
import unittest
import leap
suite "Leap":
test "year not divisible by 4 in common year":
check isLeapYear(2015) == false
test "year divisible by 4, not divisible by 100 in leap year":
check isLeapYear(1996) == true
canonical-data.json 檔案的結構有完整的文件說明,另外也有 JSON schema 定義。
有些練習的標準資料會用到巢狀結構。這代表 cases 陣列中的每個元素可能是:
你可以透過檢查某個元素是否帶有某種類型專屬的欄位,來判斷它的類型。
最好的方法大概是看 "cases" 這個鍵,它只會出現在測試案例群組裡。
以下是巢狀測試案例的範例:
{
"cases": [
{
"uuid": "e9c93a78-c536-4750-a336-94583d23fafa",
"description": "data is retained",
"property": "data",
"input": {
"treeData": ["4"]
},
"expected": {
"data": "4",
"left": null,
"right": null
}
},
{
"description": "insert data at proper node",
"cases": [
{
"uuid": "7a95c9e8-69f6-476a-b0c4-4170cb3f7c91",
"description": "smaller number at left node",
"property": "data",
"input": {
"treeData": ["4", "2"]
},
"expected": {
"data": "4",
"left": {
"data": "2",
"left": null,
"right": null
},
"right": null
}
}
]
}
]
}
如果你的 track 不支援把測試分組,你就需要:
cases 的階層,只留下最內層(葉節點)的測試案例測試案例的 input 和 expected 這兩個鍵,內容差異很大。大多數情況下,它們是純量值(例如數字、布林值或字串)或簡單的物件。不過,偶爾也會遇到比較複雜的值,可能需要先做一些前處理,例如虛擬碼中的 lambda、要對學生程式碼執行的一連串操作等等。
測試案例有一個選填的 scenarios 欄位。測試產生器可以利用這個欄位,對特定測試案例做特殊處理。最常見的用途是忽略某些類型的測試,例如標記為 "unicode" 情境的測試,因為你 track 使用的程式語言可能不支援 Unicode。
完整的情境清單可以在這裡找到。
讀取 canonical-data.json 檔案有幾種做法:
problem-specifications 儲存庫取得(例如 https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/leap/canonical-data.json)。problem-specifications 儲存庫以 Git 子模組的方式加到 track 儲存庫。configlet 快取讀取。
快取的位置取決於使用者的系統,不過你可以用 configlet info -o -v d | head -1 | cut -d " " -f 5 以程式方式取得位置。如果你的 track 想加入一些額外的、track 專屬的測試案例(標準資料裡沒有的),其中一個做法是建立一個 additional-test-cases.json 檔案,讓測試產生器先把它和 canonical-data.json 檔案合併,再傳給範本渲染。
要使用的範本引擎通常取決於 track。理想上,你會希望範本越單純越好,所以不用擔心程式碼重複之類的問題。
範本會從測試產生器取得資料,測試產生器則會疊代這些資料來渲染範本。
為了讓範本保持單純,在測試產生器這一側做一些前處理,或是定義一些「篩選器」或範本支援的擴充機制,可能會很有幫助。
configlet 是 track 的主要維護工具,可以用來:
bin/configlet create --practice-exercise <slug>
tests.toml 檔案:執行 bin/configlet sync --tests --update --exercise <slug>
因此,configlet 很適合和測試產生器搭配使用,打造出功能強大的工作流程。
你會希望使用測試產生器既簡單_又_強大。為此,我們建議建立一個或多個腳本檔案。
你可以自由選擇最適合你 track 的腳本檔案格式。 shell 腳本和 PowerShell 腳本都是常見、效果也不錯的選擇。
以下是一個 shell 腳本的範例,它結合 configlet 和測試產生器,快速建立新練習的雛形:
bin/fetch-configlet
bin/configlet create --practice-exercise <slug>
path/to/test-generator <slug>
開始建立測試產生器之前,我們建議你先看看幾個現成的測試產生器,了解其他 track 是怎麼實作的:
如果有任何問題,論壇是最好的發問地方。關於 Rust和 JavaScript 測試產生器的論壇討論,可能也很有幫助。
我們建議以漸進的方式建立測試產生器,先從最小可行產品開始。最基本可行的版本,會讀取練習的 canonical-data.json,然後直接把那份資料傳給範本。
先專注在單一練習上,最好選像 leap 這種簡單的練習。等到這個能運作之後,再逐步加入更多練習。
並且盡量讓測試產生器保持越簡單越好。
理想情況下,貢獻者只要貼上/修改現有的範本就能使用,不必了解測試產生器的內部運作方式。
使用或參與測試產生器的方式,依各個 track 而異。請在 track 的 README.md、CONTRIBUTING.md 或測試產生器程式碼所在的目錄中尋找說明。