测试你的程序
测试的重要性
Solana 程序一旦部署到主网,任何 bug 都可能导致资金损失。全面的测试是保护用户资产的关键防线。在 Web3 开发中,测试代码的数量往往是业务代码的 2-3 倍。
TypeScript 测试
TypeScript 是最常用的测试方式,因为它与前端开发共享代码库。Anchor 默认使用 Mocha 作为测试框架,Chai 作为断言库。
每个 Anchor 项目初始化时都会在 tests/ 目录创建测试文件:
tsximport * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { SolanaUniversityCounter } from "../target/types/solana_university_counter"; import { expect } from "chai"; describe("solana-university-counter", () => { // 配置客户端使用本地集群 const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.SolanaUniversityCounter as Program<SolanaUniversityCounter>; // 生成测试密钥对 const counter = anchor.web3.Keypair.generate(); it("初始化计数器", async () => { // 调用合约方法 await program.methods .initialize() .accounts({ counter: counter.publicKey, authority: provider.wallet.publicKey, systemProgram: anchor.web3.SystemProgram.programId, }) .signers([counter]) .rpc(); // 获取账户数据验证 const account = await program.account.counter.fetch(counter.publicKey); // 断言 expect(account.count.toNumber()).to.equal(0); expect(account.authority.toString()).to.equal( provider.wallet.publicKey.toString() ); }); it("增加计数", async () => { await program.methods .increment() .accounts({ counter: counter.publicKey, authority: provider.wallet.publicKey, }) .rpc(); const account = await program.account.counter.fetch(counter.publicKey); expect(account.count.toNumber()).to.equal(1); }); it("非授权用户无法增加计数", async () => { const unauthorized = anchor.web3.Keypair.generate(); try { await program.methods .increment() .accounts({ counter: counter.publicKey, authority: unauthorized.publicKey, // 使用错误的权限方 }) .signers([unauthorized]) .rpc(); expect.fail("应该抛出错误"); } catch (error) { // 验证是否抛出了预期的错误 expect(error.message).to.include("has_one"); // 或自定义错误码 } }); });
运行测试非常简单:
bashanchor test
配置本地验证器
在 Anchor.toml 中配置测试环境。你可以让本地测试网络“克隆”主网上现有的账户,这对于测试与现有 DeFi 协议(如 Raydium, Jupiter)交互的程序非常有用。
toml[test] startup_wait = 10000 # 等待验证器启动的毫秒数 [test.validator] url = "https://api.mainnet-beta.solana.com" # 克隆主网账户用于测试 (例如 USDC Mint) [[test.validator.clone]] address = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" [[test.validator.clone]] address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
Mollusk 测试
Mollusk 是 Rust 原生测试框架,适合需要精细控制测试环境的场景。它不需要启动完整的验证器,运行速度极快。
bashanchor init my-project --test-template mollusk
rust#[cfg(test)] mod tests { use super::*; use mollusk_svm::Mollusk; use solana_sdk::{account::Account, pubkey::Pubkey}; #[test] fn test_initialize() { let program_id = Pubkey::new_unique(); let mollusk = Mollusk::new(&program_id, "target/deploy/my_program"); // 设置测试账户 let counter_key = Pubkey::new_unique(); let authority_key = Pubkey::new_unique(); // 执行测试... } }
LiteSVM 测试
LiteSVM 结合了 TypeScript 的便利性和更快的执行速度。它在内存中模拟 SVM,比 solana-test-validator 快几个数量级。
bashnpm install anchor-litesvm
tsximport { fromWorkspace, LiteSVMProvider } from "anchor-litesvm"; import { Program } from "@coral-xyz/anchor"; test("使用 LiteSVM 测试", async () => { const client = fromWorkspace("target/types/my_program.ts"); const provider = new LiteSVMProvider(client); const program = new Program(IDL, provider); // 测试逻辑... });
Surfnet 测试
当需要与复杂的主网程序交互时,Surfnet 提供了按需获取主网账户的能力,类似于 Foundry 的 Anvil。
bash# 安装 Surfpool curl -sSf https://install.surfpool.run | sh # 启动 Surfnet surfpool start
tsximport { Connection } from "@solana/web3.js"; // 连接到本地 Surfnet const connection = new Connection("http://localhost:8899", "confirmed"); // 现在可以访问主网账户数据