测试你的程序

测试你的程序

测试的重要性

Solana 程序一旦部署到主网,任何 bug 都可能导致资金损失。全面的测试是保护用户资产的关键防线。在 Web3 开发中,测试代码的数量往往是业务代码的 2-3 倍。

TypeScript 测试

TypeScript 是最常用的测试方式,因为它与前端开发共享代码库。Anchor 默认使用 Mocha 作为测试框架,Chai 作为断言库。

每个 Anchor 项目初始化时都会在 tests/ 目录创建测试文件:

tsx
import * 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"); // 或自定义错误码 } }); });

运行测试非常简单:

bash
anchor 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 原生测试框架,适合需要精细控制测试环境的场景。它不需要启动完整的验证器,运行速度极快。

bash
anchor 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 快几个数量级。

bash
npm install anchor-litesvm
tsx
import { 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
tsx
import { Connection } from "@solana/web3.js"; // 连接到本地 Surfnet const connection = new Connection("http://localhost:8899", "confirmed"); // 现在可以访问主网账户数据
JSPlayground
EDITOR ACTIVE
Initializing JS Environment...

Anchor 测试运行器

tests/counter.ts
Is initialized!
Increments
Fails on unauthorized access
本地验证器 (Local Validator)
状态 (Status)● 运行中 (Running)
URLhttp://127.0.0.1:8899
Slot245,102
区块链状态 (Blockchain State)
账户未初始化 (Account not initialized)...
anchor test