Rust测试框架插件cairo-lang-test-plugin的使用,Cairo语言单元测试与智能合约自动化测试解决方案
Rust测试框架插件cairo-lang-test-plugin的使用,Cairo语言单元测试与智能合约自动化测试解决方案
安装
在项目目录中运行以下Cargo命令:
cargo add cairo-lang-test-plugin
或者在Cargo.toml中添加以下行:
cairo-lang-test-plugin = "2.12.0"
使用示例
以下是内容中提供的两个完整示例:
示例1:基本Cairo合约测试
// 首先引入必要的模块
use cairo_lang_test_plugin::TestPlugin;
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::helpers::QueryAttrs;
use cairo_lang_syntax::test_utils::create_test_syntax_db;
// 创建一个测试函数
#[test]
fn test_cairo_contract() {
// 初始化测试数据库
let db = &create_test_syntax_db();
// 创建测试插件实例
let test_plugin = TestPlugin::default();
// 定义要测试的Cairo代码
let cairo_code = r#"
%lang starknet
@contract
fn test_function() -> felt252 {
42
}
"#;
// 使用插件测试代码
let test_results = test_plugin.test_code(db, cairo_code.to_string());
// 断言测试结果
assert!(test_results.passed(), "Tests failed: {:?}", test_results);
}
示例2:智能合约完整测试
// 智能合约测试示例
#[test]
fn test_starknet_contract() {
let db = &create_test_syntax_db();
let test_plugin = TestPlugin::default();
let contract_code = r#"
%lang starknet
@storage_var
func balance() -> (res: felt252) {
}
@constructor
func constructor(initial_balance: felt252) {
balance.write(initial_balance);
}
@external
func get_balance() -> (res: felt252) {
balance.read()
}
@external
func set_balance(new_balance: felt252) {
balance.write(new_balance);
}
"#;
// 测试构造函数
let constructor_test = test_plugin.test_constructor(
db,
contract_code.to_string(),
"constructor(100)"
);
assert!(constructor_test.passed());
// 测试getter函数
let get_test = test_plugin.test_function(
db,
contract_code.to_string(),
"get_balance()"
);
assert_eq!(get_test.result().unwrap(), "100");
// 测试setter函数
let set_test = test_plugin.test_function(
db,
contract_code.to_string(),
"set_balance(200)"
);
assert!(set_test.passed());
// 验证状态变化
let updated_get_test = test_plugin.test_function(
db,
contract_code.to_string(),
"get_balance()"
);
assert_eq!(updated_get_test.result().unwrap(), "200");
}
完整示例demo
以下是一个更完整的Cairo智能合约测试示例,包含更多测试场景:
use cairo_lang_test_plugin::TestPlugin;
use cairo_lang_syntax::test_utils::create_test_syntax_db;
#[test]
fn full_contract_test_suite() {
let db = &create_test_syntax_db();
let test_plugin = TestPlugin::default();
// 定义完整的智能合约代码
let contract_code = r#"
%lang starknet
@storage_var
func counter() -> (res: felt252) {
}
@storage_var
func owner() -> (res: felt252) {
}
@constructor
func constructor(initial_owner: felt252) {
owner.write(initial_owner);
counter.write(0);
}
@external
fn increment() {
let current = counter.read();
counter.write(current + 1);
}
@external
fn get_counter() -> (res: felt252) {
counter.read()
}
@external
fn get_owner() -> (res: felt252) {
owner.read()
}
@external
fn transfer_ownership(new_owner: felt252) {
let current_owner = owner.read();
assert(current_owner == 12345, "Not owner");
owner.write(new_owner);
}
"#;
// 1. 测试构造函数
let constructor_test = test_plugin.test_constructor(
db,
contract_code.to_string(),
"constructor(12345)" // 初始化所有者地址为12345
);
assert!(constructor_test.passed());
// 2. 测试初始状态
let owner_test = test_plugin.test_function(
db,
contract_code.to_string(),
"get_owner()"
);
assert_eq!(owner_test.result().unwrap(), "12345");
let counter_test = test_plugin.test_function(
db,
contract_code.to_string(),
"get_counter()"
);
assert_eq!(counter_test.result().unwrap(), "0");
// 3. 测试递增功能
let increment_test = test_plugin.test_function(
db,
contract_code.to_string(),
"increment()"
);
assert!(increment_test.passed());
// 验证计数器值增加
let updated_counter = test_plugin.test_function(
db,
contract_code.to_string(),
"get_counter()"
);
assert_eq!(updated_counter.result().unwrap(), "1");
// 4. 测试所有权转移(成功情况)
let transfer_test = test_plugin.test_function(
db,
contract_code.to_string(),
"transfer_ownership(67890)"
);
assert!(transfer_test.passed());
// 验证新所有者
let new_owner = test_plugin.test_function(
db,
contract_code.to_string(),
"get_owner()"
);
assert_eq!(new_owner.result().unwrap(), "67890");
// 5. 测试失败场景 - 非所有者尝试转移
let fail_transfer = test_plugin.test_function(
db,
contract_code.to_string(),
"transfer_ownership(11111)"
);
assert!(!fail_transfer.passed()); // 应失败,因为当前所有者是67890不是12345
}
功能说明
cairo-lang-test-plugin 提供了以下主要功能:
- 单元测试支持:可以直接测试Cairo函数的功能和返回值
- 智能合约测试:支持测试StarkNet合约的构造函数、外部函数和存储变量
- 状态管理:可以在测试过程中跟踪和验证合约状态的变化
- 断言支持:提供多种断言方法来验证测试结果
测试报告
测试完成后,插件会生成详细的测试报告,包括:
- 通过/失败的测试数量
- 每个测试的执行结果
- 失败测试的错误信息
- 合约状态的变化历史
1 回复
Rust测试框架插件cairo-lang-test-plugin使用指南
简介
cairo-lang-test-plugin
是一个用于Cairo语言的测试框架插件,专门为Rust生态系统设计。它为Cairo智能合约和程序提供了强大的单元测试和自动化测试能力,特别适合StarkNet智能合约开发。
主要特性
- 支持Cairo语言的单元测试编写
- 提供智能合约自动化测试解决方案
- 与Rust测试框架无缝集成
- 支持测试覆盖率统计
- 提供丰富的断言功能
安装方法
在Cargo.toml中添加依赖:
[dependencies]
cairo-lang-test-plugin = "0.1.0"
完整示例演示
1. 基本测试示例
// 导入测试宏
use cairo_lang_test_plugin::test_case;
// 简单数学运算测试
#[test_case]
fn test_basic_math() {
// 加法测试
assert_eq!(1 + 1, 2, "Basic addition failed");
// 乘法测试
assert_eq!(3 * 3, 9, "Basic multiplication failed");
}
// 字符串测试
#[test_case]
fn test_string_operations() {
let hello = "Hello".to_string();
let world = "World".to_string();
assert_eq!(format!("{} {}", hello, world), "Hello World");
}
2. 智能合约测试完整示例
use cairo_lang_test_plugin::{contract_test, test_case};
use starknet::{ContractAddress, contract::Contract};
// 模拟合约部署函数
fn deploy_contract(contract_name: &str) -> Contract {
// 实际项目中会有具体实现
Contract::new(ContractAddress::from(12345))
}
// 测试合约初始化状态
#[contract_test]
fn test_contract_initial_state() {
// 部署测试合约
let contract = deploy_contract("my_contract.cairo");
// 测试初始化余额
let balance = contract.call("get_balance", &[]);
assert_eq!(balance, 0, "Initial balance should be 0");
// 测试初始化所有者
let owner = contract.call("get_owner", &[]);
assert_eq!(owner, "0x0", "Initial owner should be zero address");
}
// 测试合约方法
#[contract_test]
fn test_contract_methods() {
let contract = deploy_contract("my_contract.cairo");
// 测试存款功能
let deposit_amount = 100;
contract.call("deposit", &[deposit_amount.into()]);
let balance = contract.call("get_balance", &[]);
assert_eq!(balance, deposit_amount, "Deposit failed");
// 测试取款功能
let withdraw_amount = 50;
contract.call("withdraw", &[withdraw_amount.into()]);
let balance = contract.call("get_balance", &[]);
assert_eq!(balance, deposit_amount - withdraw_amount, "Withdraw failed");
}
3. 高级功能完整示例
测试夹具示例
use cairo_lang_test_plugin::{test_fixture, test_case};
use starknet::ContractAddress;
// 定义测试夹具
#[test_fixture]
struct TokenFixture {
token_contract: ContractAddress,
user_address: ContractAddress,
}
impl TokenFixture {
fn setup() -> Self {
// 部署代币合约
let token = deploy_contract("token.cairo");
// 创建测试用户
let user = create_test_user();
// 初始化代币供应
token.call("mint", &[user.address(), 1000]);
TokenFixture {
token_contract: token.address,
user_address: user.address,
}
}
fn teardown(&self) {
// 清理测试环境
clean_test_environment();
}
}
// 使用夹具的测试
#[test_case]
fn test_token_transfer(fixture: TokenFixture) {
// 转账前余额
let initial_balance = get_balance(fixture.token_contract, fixture.user_address);
// 执行转账
transfer(
fixture.token_contract,
fixture.user_address,
"0x1234",
100
);
// 验证余额变化
let new_balance = get_balance(fixture.token_contract, fixture.user_address);
assert_eq!(new_balance, initial_balance - 100, "Transfer failed");
}
参数化测试完整示例
use cairo_lang_test_plugin::{parameterized, test_case};
// 测试不同输入下的平方计算
#[parameterized(
input = { 0, 1, 2, 5, 10 },
expected = { 0, 1, 4, 25, 100 }
)]
#[test_case]
fn test_square(input: u64, expected: u64) {
assert_eq!(input * input, expected, "Square of {} should be {}", input, expected);
}
// 测试字符串处理
#[parameterized(
input = { "hello", "world", "test" },
expected = { 5, 5, 4 }
)]
#[test_case]
fn test_string_length(input: &str, expected: usize) {
assert_eq!(input.len(), expected, "Length of '{}' should be {}", input, expected);
}
异步测试完整示例
use cairo_lang_test_plugin::{async_test, test_case};
use std::time::Duration;
// 异步合约测试
#[async_test]
async fn test_async_contract() {
// 部署异步合约
let contract = deploy_contract("async_contract.cairo");
// 测试异步方法
let start = std::time::Instant::now();
let result = contract.call_async("long_running_method").await;
let duration = start.elapsed();
assert!(result.is_ok(), "Async call failed");
assert!(duration > Duration::from_secs(1), "Should take at least 1 second");
}
// 多个异步操作测试
#[async_test]
async fn test_multiple_async_calls() {
let contract1 = deploy_contract("contract1.cairo");
let contract2 = deploy_contract("contract2.cairo");
let future1 = contract1.call_async("method1");
let future2 = contract2.call_async("method2");
let (result1, result2) = futures::join!(future1, future2);
assert!(result1.is_ok(), "First async call failed");
assert!(result2.is_ok(), "Second async call failed");
}
测试覆盖率实践
# 生成覆盖率报告
cargo test --coverage
# 查看覆盖率报告
open target/cairo-coverage/index.html
最佳实践补充
- 测试组织:按功能模块组织测试文件
- 命名规范:测试函数名应清晰描述测试场景
- 错误处理:测试中应包含错误路径测试
- 性能测试:对关键路径添加性能测试
- Mock使用:适当使用mock替代外部依赖
// Mock示例
#[test_case]
fn test_with_mock() {
let mock_contract = create_mock_contract();
mock_contract.expect_call("get_balance")
.returns(100);
let balance = mock_contract.call("get_balance", &[]);
assert_eq!(balance, 100, "Mock balance should be 100");
}