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 提供了以下主要功能:

  1. 单元测试支持:可以直接测试Cairo函数的功能和返回值
  2. 智能合约测试:支持测试StarkNet合约的构造函数、外部函数和存储变量
  3. 状态管理:可以在测试过程中跟踪和验证合约状态的变化
  4. 断言支持:提供多种断言方法来验证测试结果

测试报告

测试完成后,插件会生成详细的测试报告,包括:

  • 通过/失败的测试数量
  • 每个测试的执行结果
  • 失败测试的错误信息
  • 合约状态的变化历史

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

最佳实践补充

  1. 测试组织:按功能模块组织测试文件
  2. 命名规范:测试函数名应清晰描述测试场景
  3. 错误处理:测试中应包含错误路径测试
  4. 性能测试:对关键路径添加性能测试
  5. 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");
}
回到顶部