测试

测试是软件开发中至关重要的一环,尤其是在区块链开发中更为重要。 在这里,我们将介绍 Move 语言的测试基础知识,并讲解如何编写和组织 Move 代码的测试。

#[test] 属性

在 Move 中,测试是使用 #[test] 属性标记的函数。 这个属性告诉编译器该函数是一个测试函数,应该在执行测试时运行。 测试函数是常规函数,但不能接受任何参数,也不能有返回值。它们不会被编译成字节码,也不会被发布。

module book::testing {
    // `#[test]` 属性放置在 `fun` 关键字之前。
    // 可以放在函数签名的上方或紧挨着 `fun` 关键字:`#[test] fun my_test() { ... }`
    // 测试的名称将会是 `book::testing::simple_test`。
    #[test]
    fun simple_test() {
        let sum = 2 + 2;
        assert!(sum == 4, 1);
    }

    // 测试的名称将会是 `book::testing::more_advanced_test`.
    #[test] fun more_advanced_test() {
        let sum = 2 + 2 + 2;
        assert!(sum == 4, 1);
    }
}

运行测试

要运行测试,可以使用 sui move test 命令。 该命令首先会在 测试模式 下构建包,然后运行包中所有找到的测试。 在测试模式下,sources/tests/ 目录中的模块都会被处理,测试也会被执行。

$ sui move test
> 包含依赖项 Sui
> 包含依赖项 MoveStdlib
> 构建 book
> 运行 Move 单元测试
> ...

使用 #[expected_failure] 处理测试失败的情况

针对失败情况的测试可以使用 #[expected_failure] 标记。 将此属性放在 #[test] 函数上,告知编译器该测试预期会失败。 当你想测试某个函数在特定条件下会失败时,这个功能非常有用。

该属性只能放在 #[test] 函数上。

该属性可以接受一个终止码作为参数,即测试失败时预期的终止码。 如果测试以不同的终止码失败,测试将失败。 如果执行过程中没有终止,测试同样会失败。

module book::testing_failure {

    const EInvalidArgument: u64 = 1;

    #[test]
    #[expected_failure(abort_code = 0)]
    fun test_fail() {
        abort 0 // 以终止码 0 终止
    }

    // 属性可以组合在一起使用。
    #[test, expected_failure(abort_code = EInvalidArgument)]
    fun test_fail_1() {
        abort 1 // 以终止码 1 终止
    }
}

abort_code 参数可以使用在测试模块中定义的常量,也可以从其他模块导入的常量。 在这种情况下,是唯一可以在其他模块中使用和“访问”常量的场景。

使用 #[test_only] 标记的工具函数

在某些情况下,让测试环境访问一些内部函数或特性是有帮助的。 它可以简化测试过程,并允许更全面的测试。 然而,需要注意的是,这些函数不应包含在最终的包中。 这时,#[test_only] 属性就派上用场了。

module book::testing {
    // 使用 `secret` 函数的公共函数
    public fun multiply_by_secret(x: u64): u64 {
        x * secret()
    }

    /// 私有函数不能被公共函数使用
    fun secret(): u64 { 100 }

    #[test_only]
    /// 此函数仅用于测试中的测试目的以及其他仅限测试的函数。
    /// 注意可见性——对于 `#[test_only]`,
    /// 通常使用 `public` 可见性。
    public fun secret_for_testing(): u64 {
        secret()
    }

    #[test]
    // 在测试环境中,我们可以访问 `secret_for_testing` 函数。
    fun test_multiply_by_secret() {
        let expected = secret_for_testing() * 2;
        assert!(multiply_by_secret(2) == expected, 1);
    }
}

使用 #[test_only] 标记的函数将在测试环境中可用, 如果它们的可见性设置允许,其他模块也可以访问这些函数。

进一步阅读