20个版本 (13个稳定版)
新 1.5.0 | 2024年8月12日 |
---|---|
1.4.2 | 2024年7月2日 |
1.4.1 | 2024年5月13日 |
1.3.1-pre.3 | 2024年4月19日 |
0.0.1 |
|
#36 in #gear
993 monthly downloads
用于 4 个crate(3个直接使用)
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
库。
我们的测试将包括以下步骤
- 初始化
System
结构。 - 初始化
Program
结构。 - 向程序发送一个初始化消息。尽管我们程序中没有
init
函数,但通过gtest
发送给程序的第一个消息总是初始化消息。 - 向程序发送一个处理消息。
- 检查程序执行的结果。
将以下行添加到 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 文件初始化程序,并使用默认 IDlet 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
发送消息
向程序发送消息需要调用程序的两个函数之一
Program::send
(或者如果您需要发送带有资金的短信,则使用Program::send_with_value
)。Program::send_bytes
(或者如果您需要发送带有资金的短信,则使用Program::send_bytes_with_value
)。
这两种方法都需要将发送者ID作为第一个参数,将有效载荷作为第二个参数。
它们之间的区别非常简单,类似于 gstd
函数 msg::send
和 msg::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