Skip to main content

單元測試工具 (Unit Test Utilities)

除了內建的 assert! 巨集,標準函式庫也提供了常用的測試工具。其中最重要的定義在 std::unit_test 模組中。雖然不是必要條件,但建議在測試中使用這個模組。

assert!

assert! 巨集是內建的語言功能,也是測試中驗證條件最基本的工具。它接受一個布林運算式,若該運算式求值為 false 則會中止。關於斷言與錯誤處理的詳細說明,請參閱中止執行

#[test]
fun test_addition() {
let sum = 2 + 2;
assert!(sum == 4);
}

在發布的程式碼中,assert! 通常應該把中止碼作為第二個參數,以協助識別失敗原因。然而,在測試中,中止碼並非必要,也不提供任何價值。

// 在發布的程式碼中 - 建議加上中止碼
assert!(balance >= amount, EInsufficientBalance);

// 在測試程式碼中 - 中止碼非必要
assert!(balance >= amount);

assert_eq!assert_ref_eq! (assert_eq! and assert_ref_eq!)

雖然 assert! 可以運作,但它有個限制:當它失敗時,只會顯示條件為 false,而不會提供任何造成失敗的實際數值的資訊。考慮以下測試:

#[test]
fun test_balance_update() {
let balance = calculate_balance();
assert!(balance == 1000); // 失敗時不會印出比較的數值
}

如果這個測試失敗了,你只會知道斷言失敗了 —— 而不知道 balance 實際上是多少。你需要加上除錯陳述式或進一步調查才能理解失敗原因。

來自 std::unit_testassert_eq! 巨集透過在斷言失敗時印出兩個值來解決這個問題:

#[test_only]
use std::unit_test::assert_eq;

#[test]
fun test_balance_update() {
let balance = calculate_balance();
assert_eq!(balance, 1000); // 失敗時印出:"Assertion failed:", 750, "!=", 1000
}

現在錯誤訊息顯示了實際值 (750) 與預期值 (1000),讓問題出在哪裡一目瞭然。這個除錯輸出之所以能運作,是因為 assert_eq! 呼叫了 std::debug::print 函式,該函式會在斷言失敗時印出這些值。

若要以參考方式比較,請使用 assert_ref_eq! 而非 assert_eq!

#[test_only]
use std::unit_test::assert_ref_eq;

#[test]
fun test_reference_equality() {
let user = get_user();
let expected = create_expected_user();
assert_ref_eq!(&user, &expected);
}

黑洞函式:destroy (Black Hole Function: destroy)

destroy 函式會消耗任何值,無論其能力(ability)為何。這對於測試沒有 drop 能力的型別而言是必要的 —— 沒有它,清理工作就需要為每個型別額外實作邏輯。

module std::unit_test;

/// 在 `test` 模式下用來銷毀任何值的黑洞函式。
public native fun destroy<T>(v: T);

考慮一個沒有 drop 的型別:

module book::ticket;

/// 一張票是一個 Object - 沒有 `drop`。
public struct Ticket has key, store {
id: UID,
event_id: u64,
seat: u64,
}

public fun new(event_id: u64, seat: u64, ctx: &mut TxContext): Ticket {
Ticket { id: object::new(ctx), event_id, seat }
}

在發布的程式碼中,Ticket 型別可能沒有刪除函式,或者要求在刪除前滿足某個條件。在這種情況下,destroy 是處理這個值的最佳方式:

#[test_only]
use std::unit_test;

#[test]
fun test_ticket_creation() {
let ctx = &mut tx_context::dummy();
let ticket = ticket::new(1, 42, ctx);

// 測試通過了 - 但我們要如何處理掉 `ticket`?

unit_test::destroy(ticket); // 消耗掉這張票
}

destroy 函式的作用就像個「黑洞」—— 它接受任何型別並讓它消失。這讓你可以專注在測試特定功能上,而不必被迫處理與測試目標無關的清理邏輯。

destroy 函式只在測試程式碼中可用,無法在正式環境的模組中使用。

在接下來的章節中,我們將涵蓋 Sui 特有的測試工具與功能。