測試執行器介面


測試執行器的單一職責,是接收一份解答、執行所有測試,並回傳標準化的輸出。 與 Exercism 網站的所有互動都會自動處理,不屬於本規格的內容。

執行

  • 測試執行器應提供一個可執行的指令碼。更多資訊請參閱 docker.md 檔案。
  • 指令碼會接收三個參數:
    • 練習的 slug(例如 two-fer)。
    • 輸入目錄的路徑(結尾帶斜線),內含提交的解答檔案及任何其他練習檔案。這個目錄應視為唯讀。技術上可以寫入其中,但暫存檔案(例如編譯原始碼)最好使用 /tmp。
    • 輸出目錄的路徑(結尾帶斜線)。這個目錄可寫入。
  • 指令碼必須在輸出目錄中寫入一個 results.json 檔案。
  • 測試執行器成功執行後,無論測試狀態為何,都必須以結束代碼 0 結束。

允許的執行時間

每個解答,測試執行器會獲得 100% CPU 與 3GB 記憶體,時間視窗為 20 秒。 20 秒後,行程會遭到中止並回報逾時。

Note

我們強烈建議遵循我們的效能最佳實務文件,以降低逾時的機率。

輸出格式

results.json 檔案支援以下欄位:

頂層

版本

鍵:version,型別:number,存在性:必填

版本:1、2、3

此檔案所遵循的規格版本:

  • 1:適用於測試執行器無法提供個別測試資訊的 track。
  • 2:適用於測試執行器能輸出個別測試資訊的 track。具有 Concept Exercise 的 track 所需的最低版本。
  • 3:適用於測試執行器能將個別測試連結至任務的 track。

狀態

鍵:status,型別:string,存在性:必填

版本:1、2、3

以下是有效的整體狀態:

  • pass:所有測試都通過
  • fail:至少有一個測試的狀態為 fail 或 error
  • error:沒有任何測試被執行(這通常表示發生編譯錯誤或語法錯誤)

error 狀態_只_應在所有測試都發生錯誤時使用。 對編譯式語言而言,這通常是程式碼無法編譯所致。 對直譯式語言而言,這是執行階段錯誤,例如導致檔案無法解析的語法錯誤。

訊息

鍵:message,型別:string,當 status = error,或當 status = fail 且 version = 1 時為必填

版本:1、2、3

當狀態為 error(沒有任何測試正確執行)時,應提供頂層的 message 鍵。它應向使用者提供所發生的錯誤。由於這是使用者除錯問題時唯一會收到的資訊,因此必須盡可能清楚:

  • 將路徑簡化為類似 <solution-dir>/relative/path,而非 /full/path/to,因為後者會包含沒有用的 ECR 特定資料
  • 在可能或適用的情況下,收合非使用者程式碼的堆疊
  • 絕不要在沒有脈絡的情況下顯示呼叫堆疊(也就是錯誤訊息)
  • 盡可能不要變更錯誤訊息,這樣會更容易搜尋錯誤

在 Ruby 中,若發生語法錯誤,我們會提供執行階段錯誤與堆疊追蹤。對編譯式語言,則應提供編譯錯誤。

頂層 message 值限制為 65535 個字元。若值包含多位元組字元,實際最大長度會更短。

當狀態不是 error 時,請將值設為 null,或完全省略該鍵。

測試

鍵:tests,型別:array,當 status = fail 或 status = pass 時為必填

版本:2、3

這是測試結果的陣列,於下方的「個別測試」一節中說明。

測試必須依照測試檔案中指定的順序回傳。對於以隨機順序執行測試的語言,這可能代表需要依照測試檔案中指定的順序重新排列結果。

其理由是,學生只會看到第一個失敗,因此顯示正確的失敗很重要。由於測試通常會以 TDD 的方式在測試檔案中排序,而且對於 Practice Exercise,學生會在編輯器中看到測試檔案,因此將結果與測試檔案對齊至關重要。

個別測試

名稱

鍵:name,型別:string,存在性:必填

版本:2、3

這是測試的名稱,以人類可讀的格式呈現。

測試程式碼

鍵:test_code,型別:string,當練習為 Concept Exercise 時為必填

版本:2、3

對於 Concept Exercise 這是必須存在的,對於 Practice Exercise 則應該存在。這項要求之所以不同,是因為學生在 Concept Exercise 中不會看到測試,因此若不顯示 test_code,可能無法完成練習;相對地,Practice Exercise 則會顯示測試。

這是受測試之指令的主體。例如,以下這個 Ruby 測試:

def test_duplicate_items_uniqs_list
  cart = ShoppingCart.new
  cart.add(:STARIC)
  cart.add(:MEDNEW)
  cart.add(:MEDNEW)
  assert_equal 'Newspaper, Rice', cart.items_list
end

應回傳如下的 test_code 值:

"cart = ShoppingCart.new
cart.add(:STARIC)
cart.add(:MEDNEW)
cart.add(:MEDNEW)
assert_equal 'Newspaper, Rice', cart.items_list"

(換行已替換為 \n 以讓 JSON 合法)。

狀態

鍵:status,型別:string,存在性:必填

版本:2、3

以下是有效的個別測試狀態:

  • pass:測試通過
  • fail:測試失敗
  • error:測試發生錯誤,也就是沒有回傳值

訊息

鍵:message,型別:string,當 status 為 fail 或 error 時為必填

版本:2、3

個別測試的 message 鍵用於回傳 status 為 fail 或 error 的測試結果。它應盡可能地人類可讀。這裡寫的任何內容,都會在學生的測試未通過時顯示給他們。如果沒有測試失敗訊息或錯誤訊息,請將值設為 null,或完全省略該鍵。在這裡輸出測試套件的輸出也是允許的。message 值沒有長度限制。

輸出

鍵:output,型別:string,存在性:選填

版本:2、3

個別測試的 output 鍵應用於存放並輸出使用者刻意為測試輸出的任何內容。

  • 它應附加到所有會產生使用者輸出的測試結果。
  • 只應顯示使用者手動輸出的內容,而非測試執行器自動產生的輸出。
  • 你可以擷取透過正常方式輸出的內容(例如 Ruby 的 puts、Python 的 print 或 C# 的 Debug.WriteLine),也可以提供一個使用者可用的方法(例如 Ruby 測試執行器提供一個全域可用的 debug 方法供使用者使用,其特性與標準的 puts 方法相同)。
  • 輸出必須限制在 500 個字元以內。無論是截斷並附上「Output was truncated. Please limit to 500 chars」這則訊息,或在這種情況下回傳錯誤,都是可以接受的。

任務 ID

鍵:task_id,型別:number,存在性:選填

版本:3

透過任務的 ID 將測試連結至特定任務,該 ID 是任務標題開頭所用的數字。只有在測試能精確連結至_一個_任務時,才將它連結至該任務。

目前只有 Concept Exercise 具有定義完善的任務可供連結測試,但未來可能會改變。

例如,請看以下這個 instructions.md 檔案:

# Instructions

You're going to write some code to help Lucian cook an exquisite lasagna from his favorite cook book.

## 1. Define the expected oven time in minutes

...

## 2. Calculate the remaining oven time in minutes

...

這些指示定義了兩個任務:

  1. 定義預期的烤箱時間(分鐘)
  2. 計算剩餘的烤箱時間(分鐘)

那麼 results.json 檔案可以有像這樣的項目:

{
  "name": "Expected oven time in minutes",
  "status": "pass",
  "task_id": 1,
  "test_code": "Assert.Equal(40, Lasagna.ExpectedMinutesInOven());"
}

這個測試現在會連結至第一個任務:「定義預期的烤箱時間(分鐘)」。請注意,名稱_不_必與任務的描述相符。

track 可以用各種方式實作這點:

  • 在測試檔案中為測試新增中繼資料(例如使用屬性、標註或註解),並讓測試執行器在執行測試時讀取這些中繼資料。
  • 將測試名稱與任務 ID 的對應關係存放在另一個檔案中(例如練習的 .meta/config.json 檔案),並將這項資訊合併到產生的 results.json 檔案中。

範例

以下是有效的 results.json 檔案在不同版本下可能的樣貌範例:

v1 範例

{
  "version": 1,
  "status": "fail",
  "message": "Failed: test_answer\nExpected: 42, actual: 3"
}

v2 範例

{
  "version": 2,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()"
    }
  ]
}

v3 範例

{
  "version": 3,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()",
      "task_id": 1
    }
  ]
}

UI/UX 相關考量

測試失敗時

當學生的解答未通過某個測試時,應顯示類似以下的內容:

Test Code:
  <test_code>

Test Result:
  <message>

測試成功時

當解答通過某個測試時,應顯示類似以下的內容:

Test Code:
  <test_code>

如何為你的語言的測試套件新增中繼資料

條條大路通羅馬,沒有規定必須採用哪種模式來達成。目前為止已採用過幾種做法:

  • 手動編寫的輔助 JSON 檔案,在測試執行期間與測試結果合併。
  • 對測試套件進行自動化靜態分析,在測試執行期間與測試結果合併。
    • 這可以透過 AST 分析或文字解析來達成