渲染zh-Hant

Errors and testing

Error handling#

Faber 將三個相關概念分開處理,而許多語言會將它們折疊成同一種形式:

構造意義
→ T一般成功回傳通道
T ∪ 無成功值域中的缺值
⇥ E錯誤的可復原替代退出通道

一般回傳#

函式 porta(整數 x) → 整數 {
    若 x ≺ 0 則 傳回 0
    傳回 x * 2
}

可失敗函式#

當函式可以透過錯誤通道離開時,使用 ⇥:

函式 divide(整數 a, 整數 b) → 整數 ⇥ 文字 {
    若 b ≡ 0 {
        拋出 "division by zero"
    }
    傳回 a / b
}

拋出 — 拋出#

拋出 將值送入錯誤通道:

函式 exigePositivum(整數 value) → 空值 ⇥ 文字 {
    若 value ≺ 0 {
        拋出 "negative value"
    }
}

復原 — 執行 / 捕捉#

呼叫端使用 執行 區塊與 捕捉 處理常式在本地復原:

函式 divide(整數 a, 整數 b) → 整數 {
    若 b ≡ 0 則 傳回 0
    傳回 a / b
}

函式 tutum(整數 a, 整數 b) → 整數 {
    執行 {
        傳回 divide(a, b)
    }
    捕捉 err {
        警告 err
        傳回 0
    }
}

直接呼叫可失敗函式並不是一般運算式。請將對 → T ⇥ E 函式的呼叫放在作用中的 執行 / 捕捉 邊界內。

內嵌轉換復原#

⊥ 也可以在 ↦ 轉換上指定內嵌復原值:

入口 {
    定值 文字 raw ← "42"
    定值 _ n ← raw ↦ 整數 ⊥ 0
}

僅效果的可失敗函式#

對於會發生錯誤但不回傳成功值的函式,省略 → T:

函式 exigePositivum(整數 value) → 空值 ⇥ 文字 {
    若 value ≺ 0 {
        拋出 "negative value"
    }
}

目前狀態#

→、傳回、⇥、拋出 與 執行 / 捕捉 都是現行的文法與檢查器 介面。Rust 與 Go 對完整 ⇥ / 拋出 / 捕捉 執行時行為的降階處理 仍是後端缺口——這些內容可以通過型別檢查,但目前尚未對所有目標產生 可失敗的執行時程式碼。

Inline testing#

Faber 將一流的測試框架內建於語言中,提供三個關鍵字:測試規格 宣告測試套件,測試 宣告單一測試案例,而 斷言 則斷言條件。測試與其所測試的程式碼位於同一檔案中,透過 faber test 執行,並支援與正式程式碼相同的編譯器流程——具備地區設定感知、型別檢查及多目標支援。

三個關鍵字#

關鍵字作用約略對應
測試規格宣告具名測試套件describe、#[cfg(test)] mod
測試宣告單一測試案例it、#[test]
斷言在執行時斷言條件assert!、assert_eq!

測試規格 — 測試套件#

測試規格 區塊會將相關的測試案例分組。套件可以巢狀,以階層方式組織測試:

測試規格 "arithmetica" {
    測試 "unum plus unum" {
        斷言 1 + 1 ≡ 2
    }

    測試 "multiplicatio" {
        斷言 3 * 4 ≡ 12
    }

    測試規格 "implicata" {
        測試 "comparatio" {
            定值 _ x ← 10
            斷言 x ≥ 10
        }
    }
}

測試 — 測試案例#

測試 區塊包含測試邏輯。它可以使用任何 Faber 程式碼——變數繫結、函式呼叫、控制流程——並以一個或多個 斷言 斷言結束。測試可以使用選用的 標籤 標記,以便選擇性執行:

測試 "echo formats operands with one space" 標籤 "coreutils" {
    斷言 echo_textus(["hello", "world"]) ≡ "hello world"
}

斷言 — 斷言#

斷言 會評估布林運算式;如果結果為假,便回報失敗。選用的訊息字串可在失敗時提供上下文:

入口 {
    定值 _ x ← 10

    # Simple assertion
    斷言 x ≻ 0

    # With custom message
    斷言 x ≡ 10 崩潰 "x decem esse debet"

    # Multiple assertions in sequence
    定值 _ 名稱 ← "Marcus"
    斷言 名稱 ≡ "Marcus"
    斷言 名稱 ≠ "" 崩潰 "nomen vacuum non sit"
}

工作流程#

測試透過 faber test 指令執行:

faber test                        # run all tests in the current package
faber test examples/coreutils/packages/echo  # run tests for a specific package

由於測試與原始碼位於同一個 .fab 檔案中,因此不需要獨立的測試目錄結構、不需要測試模組宣告,也不需要在測試建置與正式建置之間區分建置指令碼。編譯器會根據所使用的關鍵字,辨識哪些區塊是測試程式碼、哪些區塊是正式程式碼——測試規格 和 測試 會被解析,但會從正式建置中排除。

實際範例#

coreutils 的 echo 套件展示了測試框架的實際應用。測試與實作位於同一個檔案中,涵蓋選項解析、跳脫展開及邊界情況:

測試規格 "echo formatting" 標籤 "coreutils" {
    測試 "empty operands format as empty text" {
        定值 列表<文字> words ← 空集
        斷言 echo_textus(words) ≡ ""
    }

    測試 "single operand is unchanged" {
        斷言 echo_textus(["hello"]) ≡ "hello"
    }

    測試 "-E is a leading no-op option" {
        斷言 echo_textus(["-E", "hello", "world"]) ≡ "hello world"
    }

    測試 "-n suppresses the trailing newline flag" {
        斷言 echo_novam_lineam(["-n", "hello"]) ≡ 假
    }

    測試 "-e expands the declared escape subset" {
        斷言 echo_textus(["-e", "a\\nb"]) ≡ "a\nb"
        斷言 echo_textus(["-e", "a\\tb"]) ≡ "a\tb"
    }
}

設計說明#

有數項設計選擇,使 Faber 的測試框架有別於傳統方法:

  • 沒有獨立的測試二進位檔。 測試是同一份原始碼中的宣告,而不是獨立的編譯目標。編譯器會將測試區塊從正式輸出中篩除。
  • 使用標記,而不是目錄。 測試透過 標籤 標記組織,而不是目錄結構。測試可以同時屬於多個組織軸,不必為此搬移檔案。
  • 完整的編譯器流程。 測試會進行型別檢查、分析,並具備地區設定感知——相同的 --reader-locale 旗標也適用於測試輸出。
  • 多目標。 測試會透過套件所指定的後端執行——faber test --interpret 使用 MIR 逐步執行器,faber test 使用編譯後的 Rust。
  • 巢狀套件。 測試規格 區塊可以巢狀,反映其所測試程式碼的結構。

參考資料#

  1. examples/corpus/probandum/ — 測試規格 範例檔案
  2. examples/corpus/proba/ — 測試 範例檔案
  3. examples/corpus/adfirma/ — 斷言 範例檔案
  4. examples/coreutils/packages/echo/src/main.fab — 使用標記的實際範例