20个版本 (13个稳定版)

1.5.0 2024年8月12日
1.4.2 2024年7月2日
1.4.1 2024年5月13日
1.3.1-pre.32024年4月19日
0.0.1 2021年7月6日

#36 in #gear

Download history 174/week @ 2024-04-26 433/week @ 2024-05-03 322/week @ 2024-05-10 160/week @ 2024-05-17 66/week @ 2024-05-24 51/week @ 2024-05-31 83/week @ 2024-06-07 87/week @ 2024-06-14 92/week @ 2024-06-21 348/week @ 2024-06-28 252/week @ 2024-07-05 200/week @ 2024-07-12 189/week @ 2024-07-19 245/week @ 2024-07-26 196/week @ 2024-08-02 321/week @ 2024-08-09

993 monthly downloads
用于 4 个crate(3个直接使用)

GPL-3.0 许可

1MB
24K SLoC

使用gtest进行测试

gtest通过提供用户、程序、余额、邮箱等的模拟来模拟真实网络。由于它不包括实际区块链的部分,所以速度快且轻量。但是作为一个区块链网络的模型,gtest不能完全反映后者。

正如我们之前所说,gtest非常适合单元和集成测试。它还有助于调试Gear程序逻辑。基于gtest运行的测试不需要除Rust编译器之外的其他东西。在持续集成中使用时,它具有可预测性和健壮性。

主要概念

gtest是一个提供用于测试Gear程序的工具集的库。最重要的结构包括

  • System — 表示Gear网络环境的结构。它包含当前块号、时间戳和其他参数。它还存储邮箱和程序列表。
  • Program — 表示Gear程序的结构。它包含有关程序的信息,并允许向其他程序发送消息。
  • [Log] — 表示消息日志的结构。它允许检查程序执行的结果。

让我们更详细地看看如何使用gtest编写测试。

导入gtest

要使用 gtest 库,您必须将其导入到您的 Cargo.toml 文件中的 [dev-dependencies] 块中,以便只为测试而获取和编译它

[package]
name = "my-gear-app"
version = "0.1.0"
authors = ["Your Name"]
edition = "2021"

[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" }

[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" }

[dev-dependencies]
gtest = { git = "https://github.com/gear-tech/gear.git", tag = "v1.0.1" }

程序示例

让我们编写一个简单的程序,该程序将接收一条消息并回复它。

lib.rs:

#![no_std]
use gstd::msg;

#[no_mangle]
extern "C" fn handle() {
    let payload = msg::load_bytes().expect("Failed to load payload");

    if payload == b"PING" {
        msg::reply_bytes(b"PONG", 0).expect("Failed to send reply");
    }
}

build.rs:

fn main() {
    gear_wasm_builder::build();
}

我们将添加一个测试来检查程序的行为。为此,我们将使用 gtest 库。

我们的测试将包括以下步骤

  1. 初始化 System 结构。
  2. 初始化 Program 结构。
  3. 向程序发送一个初始化消息。尽管我们程序中没有 init 函数,但通过 gtest 发送给程序的第一个消息总是初始化消息。
  4. 向程序发送一个处理消息。
  5. 检查程序执行的结果。

将以下行添加到 lib.rs 文件的底部

#[cfg(test)]
mod tests {
    use gtest::{Log, Program, System};

    const USER_ID: u64 = 100001;

    #[test]
    fn test_ping_pong() {
        // Initialization of the common environment for running programs.
        let sys = System::new();

        // Initialization of the current program structure.
        let prog = Program::current(&sys);

        // Send an init message to the program.
        let res = prog.send_bytes(USER_ID, b"Doesn't matter");

        // Check whether the program was initialized successfully.
        assert!(!res.main_failed());

        // Send a handle message to the program.
        let res = prog.send_bytes(USER_ID, b"PING");

        // Check the result of the program execution.
        // 1. Create a log pattern with the expected result.
        let log = Log::builder()
            .source(prog.id())
            .dest(USER_ID)
            .payload_bytes(b"PONG");

        // 2. Check whether the program was executed successfully.
        assert!(!res.main_failed());

        // 3. Make sure the log entry is in the result.
        assert!(res.contains(&log));
    }
}

要运行测试,请使用以下命令

cargo test

gtest 功能

让我们更详细地看看 gtest 的功能。

运行程序的网络环境初始化

let sys = System::new();

这模拟了节点和链的行为。默认情况下,System::new 函数设置了以下参数

  • 当前区块等于 0
  • 当前时间戳等于您的系统 UNIX 时间戳
  • 起始消息 ID 等于 0x010000..
  • 起始程序 ID 等于 0x010000..

程序初始化

初始化程序有几种方法

  • 使用 Program::current 函数初始化当前程序

    let prog = Program::current(&sys);
    
  • 使用 Program::from_file 函数从 Wasm 文件初始化程序,并使用默认 ID

    let prog = Program::from_file(
        &sys,
        "./target/wasm32-unknown-unknown/release/demo_ping.wasm",
    );
    
  • 通过构建器初始化程序

    let prog = ProgramBuilder::from_file("your_gear_program.wasm")
        .with_id(105)
        .build(&sys);
    

    在这个库的任何地方,如果您需要指定一些 ID,它需要一个泛型类型 ID,该类型实现了 Into<ProgramIdWrapper>

    ProgramIdWrapper 可以从以下构建

    • u64
    • [u8; 32]
    • String
    • &str
    • ProgramId (来自 gear_core 的,而不是来自 gstd).

    String 实现意味着输入作为十六进制数(带或不带 "0x")。

从系统中获取程序

如果您在这个作用域、循环、其他条件(在这些条件下您没有保存结构)之外初始化程序,您可以通过 ID 从系统获取对象。

let prog = sys.get_program(105).unwrap();

初始化带样式的 env_logger

初始化带样式的 env_logger 以将日志(默认情况下仅为 gwasm)打印到 stdout

sys.init_logger();

要指定打印的日志,设置环境变量 RUST_LOG

RUST_LOG="target_1=logging_level,target_2=logging_level" cargo test

发送消息

向程序发送消息需要调用程序的两个函数之一

这两种方法都需要将发送者ID作为第一个参数,将有效载荷作为第二个参数。

它们之间的区别非常简单,类似于 gstd 函数 msg::sendmsg::send_bytes

第一个需要将有效载荷编码为 CODEC 可编码的,而第二个需要有效载荷实现 AsRef<[u8]>,这意味着可以表示为字节。

Program::send 使用 Program::send_bytes 在底层,使用来自 payload.encode() 的字节。

初始化程序结构后的第一条消息总是初始化消息。

let res = prog.send_bytes(100001, "INIT MESSAGE");

处理程序执行结果

库中的任何发送函数都返回 RunResult 结构。

它包含处理消息的最终结果以及其他在执行期间创建的内容。

它有 4 个主要功能

  • RunResult::log — 返回用户消息产生的 Vec 的引用。您可以根据需要断言它们,遍历它们。
  • RunResult::main_failed — 返回布尔值,表示在主消息执行期间发生 panic。
  • RunResult::others_failed — 返回布尔值,表示在主执行期间创建的消息执行期间发生 panic。如果没有其他调用,则等于 false。
  • RunResult::contains — 返回一个布尔值,表示日志中包含指定的日志。语法糖围绕 res.log().iter().any(|v| v == arg)

为了构建断言日志,您需要使用 [Log] 结构及其构建器。这里所有字段都是可选的。使用核心 Log 结构进行的断言是在 Some(..) 字段上进行的。如果您尝试设置已指定的字段,将会遇到 panic。

// Constructor for success log.
let log = Log::builder();

// Constructor for error reply log.
let log = Log::error_builder(ErrorReplyReason::InactiveActor);
// Other fields are set optionally by `dest()`, `source()`, `payload()`, `payload_bytes()`.
let log = Log::builder()
    .source(prog.id())
    .dest(100001)
    .payload_bytes("PONG");

日志还具有来自 (ID, T) 和来自 (ID_1, ID_2, T)From 实现,其中 ID: Into<ProgramIdWrapper>T: AsRef<[u8]>

let x = Log::builder().dest(5).payload_bytes("A");
let x_from: Log = (5, "A").into();
assert_eq!(x, x_from);

let y = Log::builder().dest(5).source(15).payload_bytes("A");
let y_from: Log = (15, 5, "A").into();
assert_eq!(y, y_from);

消耗区块

您可以通过消耗区块来控制系统中的时间。

它将作为参数传递的区块数量添加到系统的当前区块。同样适用于时间戳。注意,目前 Gear-based 网络中的 1 个区块持续 3 秒。

// Spend 150 blocks (7.5 mins for 3 sec block).
sys.spend_blocks(150);

请注意,处理消息(例如,使用 Program::send/Program::send_bytes 方法)不会消耗区块,也不会更改时间戳。如果您编写了依赖于时间的逻辑,您应该手动消耗区块。

余额

// If you need to send a message with value you have to mint balance for the message sender:
let user_id = 42;
sys.mint_to(user_id, 5000);
assert_eq!(sys.balance_of(user_id), 5000);

// To give the balance to the program you should use `mint` method:
let mut prog = Program::current(&sys);
prog.mint(1000);
assert_eq!(prog.balance(), 1000);

依赖项

~71MB
~1.5M SLoC