Move 的單元測試為 Move 源語言添加了三個新註釋
它們分別將函數標記為測試,將模塊或模塊成員(use
、函數或結構)標記為僅用於測試的代碼,並標記預期測試將失敗。這些註釋可以放置在具有任何可見性的函數上。每當一個模塊或模塊成員被註釋為#[test_only]
or#[test]
時,它不會被包含在編譯中,除非它被編譯用於測試。
#[test] // OK
fun this_is_a_test() { ... }
#[test] // 失敗,因為有參數
fun this_is_not_correct(arg: signer) { ... }
測試也可以註釋為#[expected_failure]
此註釋標誌著測試會引發錯誤。可以通過使用註釋來確保測試使用特定的中止代碼中止#[expected_failure(abort_code = <code>)]
。只有具有#[test]
註釋的函數也可以註釋為 # [expected_failure]
。
#[test]
#[expected_failure]
public fun this_test_will_abort_and_pass() { abort 1 }
#[test]
#[expected_failure]
public fun test_will_error_and_pass() { 1/0; }
#[test]
#[expected_failure(abort_code = 0)]
public fun test_will_error_and_fail() { 1/0; }
#[test, expected_failure] // Can have multiple in one attribute. This test will pass.
public(script) fun this_other_test_will_abort_and_pass() { abort 1 }
帶有參數的測試註解採用#[test(<param_name_1> = <address>, ..., <param_name_n> = <address>)]
. 如果以這種方式註釋函數,則函數的參數必須是參數 < 的排列param_name_1>, ..., <param_name_n>
,即這些參數在函數中出現的順序和它們在測試註釋中的順序不必相同,但它們必須能夠通過名稱相互匹配。
#[test(arg = @0xC0FFEE)] // OK
fun this_is_correct_now(arg: signer) { ... }
#[test(wrong_arg_name = @0xC0FFEE)] // Not correct: arg name doesn't match
fun this_is_incorrect(arg: signer) { ... }
#[test(a = @0xC0FFEE, b = @0xCAFE)] // OK. We support multiple signer arguments, but you must always provide a value for that argument
fun this_works(a: signer, b: signer) { ... }
// somewhere a named address is declared
#[test_only] // test-only named addresses are supported
address TEST_NAMED_ADDR = @0x1;
...
#[test(arg = @TEST_NAMED_ADDR)] // Named addresses are supported!
fun this_is_correct_now(arg: signer) { ... }
一個模塊及其任何成員都可以聲明為僅測試。在這種情況下,只有在測試模式下編譯時,該項目才會包含在編譯後的 Move 字節碼中。use
此外,在測試模式之外編譯時,模塊的任何非 test#[test_only]
都會在編譯期間引發錯誤。
#[test_only] // test only attributes can be attached to modules
module ABC { ... }
#[test_only] // test only attributes can be attached to named addresses
address ADDR = @0x1;
#[test_only] // .. to uses
use 0x1::SomeOtherModule;
#[test_only] // .. to structs
struct SomeStruct { ... }
#[test_only] // .. and functions. Can only be called from test code, but not a test
fun test_only_function(...) { ... }
可以使用move package test
指令運行 Move 包的單元測試。
$ move package -h
$ move package new TestExample;
$ cd TestExample
sources
目錄下添加以下模塊:// filename: sources/MyModule.move
module 0x1::MyModule {
struct MyCoin has key { value: u64 }
public fun make_sure_non_zero_coin(coin: MyCoin): MyCoin {
assert!(coin.value > 0, 0);
coin
}
public fun has_coin(addr: address): bool {
exists<MyCoin>(addr)
}
#[test]
fun make_sure_non_zero_coin_passes() {
let coin = MyCoin { value: 1 };
let MyCoin { value: _ } = make_sure_non_zero_coin(coin);
}
#[test]
// Or #[expected_failure] if we don't care about the abort code
#[expected_failure(abort_code = 0)]
fun make_sure_zero_coin_fails() {
let coin = MyCoin { value: 0 };
let MyCoin { value: _ } = make_sure_non_zero_coin(coin);
}
#[test_only] // test only helper function
fun publish_coin(account: &signer) {
move_to(account, MyCoin { value: 1 })
}
#[test(a = @0x1, b = @0x2)]
fun test_has_coin(a: signer, b: signer) {
publish_coin(&a);
publish_coin(&b);
assert!(has_coin(@0x1), 0);
assert!(has_coin(@0x2), 1);
assert!(!has_coin(@0x3), 1);
}
}
$ move package test
BUILDING MoveStdlib
BUILDING TestExample
Running Move unit tests
[ PASS ] 0x1::MyModule::make_sure_non_zero_coin_passes
[ PASS ] 0x1::MyModule::make_sure_zero_coin_fails
[ PASS ] 0x1::MyModule::test_has_coin
Test result: OK. Total tests: 3; passed: 3; failed: 0
只測試包含特定名稱的函數
-f <str>或者--filter <str>
只測試包含 zero_coin
名字的函數
$ move package test -f zero_coin
CACHED MoveStdlib
BUILDING TestExample
Running Move unit tests
[ PASS ] 0x1::MyModule::make_sure_non_zero_coin_passes
[ PASS ] 0x1::MyModule::make_sure_zero_coin_fails
Test result: OK. Total tests: 2; passed: 2; failed: 0
收集有關測試運行的統計資料
$ move package test -s
CACHED MoveStdlib
BUILDING TestExample
Running Move unit tests
[ PASS ] 0x1::MyModule::make_sure_non_zero_coin_passes
[ PASS ] 0x1::MyModule::make_sure_zero_coin_fails
[ PASS ] 0x1::MyModule::test_has_coin
Test Statistics:
┌───────────────────────────────────────────────┬────────────┬───────────────────────────┐
│ Test Name │ Time │ Instructions Executed │
├───────────────────────────────────────────────┼────────────┼───────────────────────────┤
│ 0x1::MyModule::make_sure_non_zero_coin_passes │ 0.009 │ 1 │
├───────────────────────────────────────────────┼────────────┼───────────────────────────┤
│ 0x1::MyModule::make_sure_zero_coin_fails │ 0.008 │ 1 │
├───────────────────────────────────────────────┼────────────┼───────────────────────────┤
│ 0x1::MyModule::test_has_coin │ 0.008 │ 1 │
└───────────────────────────────────────────────┴────────────┴───────────────────────────┘
Test result: OK. Total tests: 3; passed: 3; failed: 0
在區塊鏈上,需要更加小心所撰寫的程式碼是否有任何非預期的 BUG, 良好的測試和覆蓋率可以盡量減低風險,本篇一定要好好掌握。讓我們 Move to Day 26