区块链 区块链技术 比特币公众号手机端

什么是 Bankrun 以及如何使用它来赋能 Solana 开发

liumuhui 8个月前 (01-28) 阅读数 122 #区块链
文章标签 BankrunSolana
  • 原文链接:www.quicknode.com/guides...
  • 译者:AI翻译官,校对:翻译小组
  • 本文链接:learnblockchain.cn/article…

概述

Bankrun 是一个快速、强大且轻量级的框架,用于在 NodeJS 中测试 Solana 程序。它解决了测试 Solana 程序时常见的开发者痛点,从而能够为你节省时间。让我们看看它能为你做些什么以及如何开始使用它!

你将做什么

  • 了解 Bankrun 是什么以及它如何帮助你测试 Solana 程序
  • 创建一个依赖于特定时间和基于账户约束的新 Anchor 程序
  • 使用 Bankrun 编写测试以验证程序的功能

你将需要什么

  • 对 Solana 基础知识 的基本理解
  • 使用 Solana 本地验证器的经验( 指南:如何设置本地验证器 )
  • 使用 Anchor 的经验( 指南:如何编写你的第一个 Anchor 程序 )
  • TypeScript 和 Rust 经验
依赖项 版本
Solana cli 1.18.8
Anchor CLI 0.30.1
Node.js 最新版
yarn 最新版
ts-node 最新版
typescript 最新版
Rust 最新版

什么是 Bankrun?

Bankrun 是 Solana CLI 测试验证器(通过 solana-test-validator 运行)的替代方案,旨在提供更快和更灵活的测试。它提供了几个功能,使你能够更轻松地测试 Solana 程序,包括:

  • 更快的测试验证
  • "时间旅行" 来修改区块链的状态
  • 任意账户数据(这在使用主网的代币铸造和其他相关账户时非常有用)

Bankrun 通过启动一个轻量级的 BanksServer 工作,这几乎就像一个轻量级的 RPC,并创建一个 BanksClient 来与服务器进行通信。

如何使用 Bankrun?

让我们创建一个简单的 Anchor 项目,以测试一些 Bankrun 的功能。在开始之前,请确保你已安装 Anchor 版本 0.30.1 或更高版本。你可以通过运行以下命令检查版本:

anchor --version

如果你还没有,可以按照 这里 的安装说明进行操作。

创建新项目

请在终端中创建一个新项目。从项目父目录中运行以下命令:

anchor init bankrun-test

然后切换到新目录:

cd bankrun-test

项目创建后,请构建程序以确保一切正常:

anchor build

初始构建在几分钟后应成功完成。如果你看到错误,请按照错误消息中的说明进行修复。

安装依赖项

我们需要一些依赖项以使项目正常工作。让我们来安装它们:

首先,安装我们的 Node.js 依赖项:

    yarn add solana-bankrun anchor-bankrun @solana/spl-token

这将允许我们在测试中使用 Bankrun 以及 Solana 代币程序。

接下来,让我们将 SPL 代币程序添加到我们的 Anchor 程序。导航到 programs/bankrun-test/Cargo.toml 并将 anchor-spl 添加到依赖项:

    [dependencies]
    anchor-lang = "0.30.1"
    anchor-spl = "0.30.1"

由于我们使用的是 Anchor 0.30 以上版本,我们还需要更新 programs/bankrun-test/Cargo.toml 中的 idl-build 功能,以包含 anchor-spl 依赖项:

    idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]

太好了!我们现在准备好开始构建我们的程序。

创建 Anchor 程序

由于本指南的重点是使用 Bankrun 进行测试,我们不会花太多时间在 Anchor 程序本身上。相反,我们将重点放在编写 Bankrun 测试上。导航到 programs/bankrun-test/src/lib.rs 并将内容替换为以下内容(确保将程序 ID 替换为你自己的):

use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;
use std::str::FromStr;

declare_id!("11111111111111111111111111111111"); // REPLACE WITH YOUR PROGRAM ID

const MINIMUM_SLOT: u64 = 100;
const TOKEN_MINIMUM_BALANCE: u64 = 100_000_000_000;
const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

#[program]
pub mod bankrun_test {
    use super::*;

    pub fn set_data(ctx: Context<SetData>) -> Result<()> {
        let current_slot = Clock::get()?.slot;
        msg!("Current slot: {}", current_slot);
        require_gte!(current_slot, MINIMUM_SLOT, BankrunError::InvalidSlot);

        ctx.accounts.data_account.new_data = ctx.accounts.new_data.key();
        ctx.accounts.data_account.last_updated_slot = current_slot;
        msg!("Set new data: {}", ctx.accounts.new_data.key());

        Ok(())
    }

    pub fn check_spl_token(ctx: Context<CheckSplToken>) -> Result<()> {
        let usdc_mint = Pubkey::from_str(USDC_MINT).unwrap();

        let token_account = &ctx.accounts.token_account;
        let token_balance = token_account.amount;

        msg!("Token account: {} has a balance of {}", token_account.key(), token_balance);
        require_keys_eq!(token_account.mint, usdc_mint, BankrunError::InvalidTokenMint);
        require_gte!(token_balance, TOKEN_MINIMUM_BALANCE, BankrunError::InsufficientTokenBalance);

        Ok(())
    }
}

#[derive(Accounts)]
pub struct SetData<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,

    #[account(
        init,
        payer = payer,
        space = 8 + DataAccount::INIT_SPACE
    )]
    pub data_account: Account<'info, DataAccount>,

    pub new_data: Signer<'info>,

    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct CheckSplToken<'info> {
    pub token_account: Account<'info, TokenAccount>,
}

#[account]
#[derive(InitSpace)]
pub struct DataAccount {
    pub last_updated_slot: u64,
    pub new_data: Pubkey
}

#[error_code]
pub enum BankrunError {
    // Error code: 6000
    #[msg("Invalid slot")]
    InvalidSlot,
    // Error code: 6001
    #[msg("Insufficient token balance")]
    InsufficientTokenBalance,
    // Error code: 6002
    #[msg("Invalid token mint")]
    InvalidTokenMint,
}

你可以运行 anchor keys sync 来更新你的 declare_id! 宏,以匹配你程序的程序 ID。

让我们来看一下我们程序的每个指令:

  • set_data:该指令将 DataAccountlast_updated_slot 字段设置为当前槽,并将 new_data 字段设置为签署交易的 new_data 账户的公钥。该指令检查当前槽是否大于或等于 MINIMUM_SLOT 常量。如果当前槽小于 MINIMUM_SLOT 常量,则该指令将失败并显示错误代码 InvalidSlot
  • check_spl_token:该指令检查 token_account 的余额是否大于或等于 TOKEN_MINIMUM_BALANCE 常量。它还检查 token_account 是否与 USDC_MINT 常量相关联。如果这些条件之一不满足,则该指令将失败并显示错误代码 InsufficientTokenBalanceInvalidTokenMint

这应该足以帮助我们演示 Bankrun 的“时间旅行”能力,并向账户写入任意数据。

继续运行 anchor build 来构建程序,并确保它成功编译。你不应该看到任何错误,但如果你看到,请按照终端中的说明进行修复。

编写测试

好的!让我们开始编写测试。导航到 tests/bankrun-test.ts 并删除现有内容。

导入依赖

让我们开始导入必要的依赖。在文件顶部添加以下内容:

import { setProvider, Program } from "@coral-xyz/anchor";
import { BankrunTest } from "../target/types/bankrun_test";
import {
  AccountInfoBytes,
  AddedAccount,
  BanksClient,
  BanksTransactionResultWithMeta,
  ProgramTestContext,
  startAnchor
} from "solana-bankrun";
import { BankrunProvider } from "anchor-bankrun";
import { expect } from "chai";
import {
  PublicKey,
  Transaction,
  Keypair,
  Connection,
  clusterApiUrl,
  TransactionInstruction
} from "@solana/web3.js";
import {
  ACCOUNT_SIZE,
  AccountLayout,
  getAssociatedTokenAddressSync,
  MintLayout,
  TOKEN_PROGRAM_ID
} from "@solana/spl-token";

const IDL = require("../target/idl/bankrun_test.json");

这些内容中的大部分应该看起来很熟悉,但我们从 anchor-bankrunsolana-bankrun 中导入了一些新的依赖。我们将在设置函数中讨论这些依赖。

定义常量

让我们创建几个常量来帮助我们进行测试。在导入下方添加以下内容:

// Constants
const PROJECT_DIRECTORY = ""; // Leave empty if using default anchor project
const USDC_DECIMALS = 6;
const USDC_MINT_ADDRESS = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
const MINIMUM_SLOT = 100;
const MINIMUM_USDC_BALANCE = 100_000_000_000; // 100k USDC
  • PROJECT_DIRECTORY: 这是你的 Anchor 项目所在的目录(与 Anchor.toml 在同一目录)。如果你使用默认项目,可以将其留为空字符串。
  • USDC_DECIMALS: 这是 USDC 代币的小数位数。
  • USDC_MINT_ADDRESS: 这是 USDC 代币铸币的地址。
  • MINIMUM_SLOT: 这是我们在 Anchor 程序中定义的相同最小Slot。
  • MINIMUM_USDC_BALANCE: 这是我们在 Anchor 程序中定义的相同的 USDC 最低余额。

设置函数​

接下来,让我们创建一些设置函数来帮助我们进行测试。在常量下方添加以下内容:

async function createAndProcessTransaction(
  client: BanksClient,
  payer: Keypair,
  instruction: TransactionInstruction,
  additionalSigners: Keypair[] = []
): Promise<BanksTransactionResultWithMeta> {
  const tx = new Transaction();
  const [latestBlockhash] = await client.getLatestBlockhash();
  tx.recentBlockhash = latestBlockhash;
  tx.add(instruction);
  tx.feePayer = payer.publicKey;
  tx.sign(payer, ...additionalSigners);
  return await client.tryProcessTransaction(tx);
}

async function setupATA(
  context: ProgramTestContext,
  usdcMint: PublicKey,
  owner: PublicKey,
  amount: number
): Promise<PublicKey> {
  const tokenAccData = Buffer.alloc(ACCOUNT_SIZE);
  AccountLayout.encode(
    {
      mint: usdcMint,
      owner,
      amount: BigInt(amount),
      delegateOption: 0,
      delegate: PublicKey.default,
      delegatedAmount: BigInt(0),
      state: 1,
      isNativeOption: 0,
      isNative: BigInt(0),
      closeAuthorityOption: 0,
      closeAuthority: PublicKey.default,
    },
    tokenAccData,
  );

  const ata = getAssociatedTokenAddressSync(usdcMint, owner, true);
  const ataAccountInfo = {
    lamports: 1_000_000_000,
    data: tokenAccData,
    owner: TOKEN_PROGRAM_ID,
    executable: false,
  };

  context.setAccount(ata, ataAccountInfo);
  return ata;
}

让我们看看这些函数的每一个:

  • createAndProcessTransaction 接受一些基本交易信息和一个 BanksClient,这是一个从任意验证者的角度表示账本状态的客户端(当我们使用 startAnchor 功能初始化测试时将创建它)。然后它创建一个新交易,将提供的指令添加到其中,用提供的付款人进行签名,并使用 BanksClienttryProcessTransaction 函数处理它。这个函数对于测试很有用,因为它返回 BanksTransactionResultWithMeta,其中包含交易日志、返回数据、使用的计算单位以及(如果适用)错误。
  • setupATA 接受一个 ProgramTestContext,它实际上是 BanksClient 的扩展,包含一些附加功能,包括 setAccount 函数,允许我们在上下文中设置一个账户。该函数将提供的账户数据编码到缓冲区,创建一个与提供的拥有者和铸币相对应的关联代币账户(ATA),并将 ATA 的数据设置为编码后的缓冲区。然后它将返回 ATA 的公钥。

构建我们的测试环境

现在我们有了设置函数,可以开始编写测试。在支持函数下方描述测试套件:

describe("Bankrun Tests", () => {
  const usdcMint = new PublicKey(USDC_MINT_ADDRESS);
  let context: ProgramTestContext;
  let client: BanksClient;
  let payer: Keypair;
  let provider: BankrunProvider;
  let program: Program<BankrunTest>;

  before(async () => {
    const connection = new Connection(clusterApiUrl("mainnet-beta"));
    const accountInfo = await connection.getAccountInfo(usdcMint);
    const usdcAccount: AddedAccount = { address: usdcMint, info: accountInfo };

    context = await startAnchor(PROJECT_DIRECTORY, [], [usdcAccount]);
    client = context.banksClient;
    payer = context.payer;
    provider = new BankrunProvider(context);
    setProvider(provider);
    program = new Program<BankrunTest>(IDL, provider);
  });

  // TODO: Add Time Travel Tests Here

  // TODO: Add Arbitrary Data Account Tests Here

});

在这里我们定义了一些将在测试中全局使用的变量:

  • usdcMint: 这是 USDC 代币铸币的公钥。
  • context: 这是 ProgramTestContext 的一个实例,绕过 BanksClient 并包含附加功能。这个实例是通过 startAnchor 函数初始化的。请注意,我们传入了 usdcAccount。这将初始化我们的测试环境,并初始化 USDC 铸造账户(我们稍后会编写测试来验证这一点)。
  • client: 这是将用于与账本状态互动的 BanksClient
  • payer: 这是将用于签署交易的付款人。
  • provider: 这是 BankrunProvider 的一个实例(实现了 Anchor 的 Provider),它将包含附加的上下文和功能。
  • program: 这是 Anchor 的 Program<BankrunTest> 的一个实例,将像任何其他 Anchor 测试套件一样使用。

时间旅行测试

让我们创建一些测试,帮助我们验证程序的功能。由于我们有一个指令,如果当前Slot小于 MINIMUM_SLOT 则会失败,所以在传统测试环境中这个测试可能会很麻烦。Bankrun 允许我们根据需要进行“时间旅行”,这可以通过两种方式实现:

  1. 使用 provider.context.warpToSlot 函数(或对时间段使用 warpToEpoch),或
  2. 使用 context.setClock 函数。

在你的 "Bankrun Tests" 描述块内, 在 "before" 块 *之后,添加以下 "Time Travel Tests" 描述块:

describe("Time Travel Tests", () => {
    const testCases = [
        { desc: "(too early)", slot: MINIMUM_SLOT - 1, shouldSucceed: false },
        { desc: "(at or above threshold)", slot: MINIMUM_SLOT, shouldSucceed: true },
    ]
    testCases.forEach(({ desc, slot, shouldSucceed }) => {
        describe(`When slot is ${slot} ${desc}`, () => {
            let txResult: BanksTransactionResultWithMeta;
            let newData: Keypair;
            let dataAccount: Keypair;
            before(async () => {
                provider.context.warpToSlot(BigInt(slot));
                newData = Keypair.generate();
                dataAccount = Keypair.generate();
                const ix = await program.methods
                    .setData()
                    .accounts({
                        payer: payer.publicKey,
                        newData: newData.publicKey,
                        dataAccount: dataAccount.publicKey,
                    })
                    .signers([newData, dataAccount])
                    .instruction();
                txResult = await createAndProcessTransaction(client, payer, ix, [newData, dataAccount]);
            });
            if (!shouldSucceed) {
                it("transaction should fail", () => {
                    expect(txResult.result).to.exist;
                });
                it("should contain specific error details in log", () => {
                    const errorLog = txResult.meta.logMessages.find(log =>
                        log.includes('AnchorError') &&
                        log.includes('InvalidSlot') &&
                        log.includes('6000') &&
                        log.includes('Error Message: Invalid slot')
                    );
                    expect(errorLog).to.exist;
                });
                it("last log message should indicate failure", () => {
                    expect(txResult.meta.logMessages[txResult.meta.logMessages.length - 1]).to.include('failed');
                });
            } else {
                it("transaction should succeed", () => {
                    expect(txResult.result).to.be.null;
                });
                it("last log message should indicate success", () => {
                    expect(txResult.meta.logMessages[txResult.meta.logMessages.length - 1]).to.include('success');
                });
                it("should contain expected log message", () => {
                    const expectedLog = "Set new data: " + newData.publicKey.toString();
                    const foundLog = txResult.meta.logMessages.some(log => log.includes(expectedLog));
                    expect(foundLog).to.be.true;
                });
                it("should set new data correctly", async () => {
                    const onChainData = await program.account.dataAccount.fetch(dataAccount.publicKey);
                    expect(onChainData.newData.toString()).to.equal(newData.publicKey.toString());
                });
            }
        });
    });
});

让我们来分析一下测试:

  • 首先,我们定义了一个测试用例数组。每个测试用例都是一个对象,包含描述、槽号和一个布尔值,指示测试应该成功还是失败。在这种情况下,我们测试一个当前槽少于 MINIMUM_SLOT 常量的情况(预期失败),以及一个当前槽大于或等于 MINIMUM_SLOT 常量的情况(预期成功)。对于每个测试用例,我们创建一个 describe 块来描述测试用例。在 describe 块中,我们创建一个 before 块来设置测试环境。在创建和发送交易之前,我们使用 provider.context.warpToSlot 函数“时光旅行”到指定的槽。然后,根据指令是否期望成功或失败,我们在 describe 块内运行一系列测试。对于每个测试,我们创建一个 it 块来描述测试用例。在 it 块内,我们检查交易结果并对期望的行为进行断言。
  • createAndProcessTransaction 函数只有在交易过程中发生错误时返回 result 属性,因此我们可以使用 expect 函数来检查结果是否为 null。此外,createAndProcessTransaction 函数将我们程序的日志消息作为 meta.logMessages 属性返回。我们创建了一些辅助函数来查找预期的 AnchorError 日志或预期的 successfailed 日志消息。
  • 最后,我们使用 Anchor fetch 方法从 dataAccount 账户中获取 newData 字段,并断言它与预期值匹配。

现在我们应该能够运行测试。在终端中输入:

anchor test

你的测试应通过,但你应该注意到终端中包含大量调试信息:

When slot is 100 (at or above threshold)
[2024-07-17T21:57:31.175139000Z DEBUG solana_runtime::message_processor::stable_log] Program 4uqt4ZDm7WhG3BfQASEADZNi5TUyE21zwXwbqUb1Qjmk invoke [1]
[2024-07-17T21:57:31.175203000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Instruction: SetData
[2024-07-17T21:57:31.175246000Z DEBUG solana_runtime::message_processor::stable_log] Program 11111111111111111111111111111111 invoke [2]
[2024-07-17T21:57:31.175254000Z DEBUG solana_runtime::message_processor::stable_log] Program 11111111111111111111111111111111 success
[2024-07-17T21:57:31.175290000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Current slot: 100
[2024-07-17T21:57:31.175372000Z DEBUG solana_runtime::message_processor::stable_log] Program log: Set new data: 5go3aphd2yJPqRpQEN8Pg4Asvp2xkpNLJxXc6HErm4a5
[2024-07-17T21:57:31.175383000Z DEBUG solana_runtime::message_processor::stable_log] Program 4uqt4ZDm7WhG3BfQASEADZNi5TUyE21zwXwbqUb1Qjmk consumed 18600 of 200000 compute units
[2024-07-17T21:57:31.175391000Z DEBUG solana_runtime::message_processor::stable_log] Program 4uqt4ZDm7WhG3BfQASEADZNi5TUyE21zwXwbqUb1Qjmk success
         transaction should succeed
         last log message should indicate success
         should contain expected log message
         should set new data correctly

这又是使用 Bankrun 的一个好处,因为它允许我们更细致地调试测试。尽管这不是什么大问题,因为我们的测试是通过的,但在快速调试程序时,这可以非常有用!你应该能够浏览日志,准确查看你的程序如何以及在哪里记录错误或成功消息(正如我们在测试中识别的那样)。非常酷,对吧?

任意数据账户测试

现在让我们看一些测试,以帮助我们探索 BankRun 的任意数据账户功能。我们将测试 check_spl_token 指令,该指令将检查代币账户的余额是否大于或等于 TOKEN_MINIMUM_BALANCE 常量。如果你还记得,我们的 Anchor 程序要求代币铸造必须是 USDC 铸造。运行此测试通常需要使用一个假的 USDC 代币,但 Bankrun 允许我们使用任意账户,这意味着我们可以将代币账户信息写入任何我们想要的账户。事实上,我们已经在我们的 startAnchor 函数中做到这一点——通过使用我们从 Solana 主网提取的账户信息来初始化 usdcMint 账户。让我们(1)编写一个测试以确保 USDC 铸造正确初始化,并(2)编写一组测试以验证具有足够余额的 ATA 是否已初始化。在你的 "Bankrun Tests" 描述块内, 在 "Time Travel Tests" 块后,添加以下 "Arbitrary Data Account Tests" 描述块:

describe("任意数据账户测试", () => {
  const testCases = [
    { desc: "余额不足", amount: MINIMUM_USDC_BALANCE - 1_000_000, shouldSucceed: false },
    { desc: "余额充足", amount: MINIMUM_USDC_BALANCE, shouldSucceed: true },
  ];
  describe("USDC 铸造初始化", () => {
    let rawAccount: AccountInfoBytes;
    before(async () => {
      rawAccount = await client.getAccount(usdcMint);
    });
    it("应当已经初始化 USDC 铸造", () => {
      expect(rawAccount).to.exist;
    });
    it("应当有正确的小数位数", () => {
      const mintInfo = MintLayout.decode(rawAccount.data);
      expect(mintInfo.decimals).to.equal(USDC_DECIMALS);
    });
  });
  testCases.forEach(({ desc, amount, shouldSucceed }) => {
    describe(`拥有 ${desc} USDC 余额的 ATA`, () => {
      let ata: PublicKey;
      let txResult: BanksTransactionResultWithMeta;
      before(async () => {
        let owner = Keypair.generate();
        ata = await setupATA(context, usdcMint, owner.publicKey, amount);
        const ix = await program.methods
          .checkSplToken()
          .accounts({
            tokenAccount: ata,
          })
          .instruction();
        txResult = await createAndProcessTransaction(client, payer, ix);
      });
      it("应当已经初始化 USDC ATA", async () => {
        const rawAccount = await client.getAccount(ata);
        expect(rawAccount).to.exist;
      });
      it("应当在 ATA 中有正确的余额", async () => {
        const accountInfo = await client.getAccount(ata);
        const tokenAccountInfo = AccountLayout.decode(accountInfo.data);
        expect(tokenAccountInfo.amount).to.equal(BigInt(amount));
      });
      if (shouldSucceed) {
        it("应当成功处理交易", () => {
          expect(txResult.result).to.be.null;
        });
        it("最后的日志消息应指示成功", () => {
          expect(txResult.meta.logMessages[txResult.meta.logMessages.length - 1]).to.include('success');
        });
      } else {
        it("应当无法处理交易", () => {
          expect(txResult.result).to.exist;
        });
        it("应在日志中包含特定错误细节", () => {
          const errorLog = txResult.meta.logMessages.find(log =>
            log.includes('AnchorError') &&
            log.includes('InsufficientTokenBalance') &&
            log.includes('6001') &&
            log.includes('错误消息: 代币余额不足。')
          );
          expect(errorLog).to.exist;
        });
        it("最后的日志消息应指示失败", () => {
          expect(txResult.meta.logMessages[txResult.meta.logMessages.length - 1]).to.include('failed');
        });
      }
    });
  });
});

测试分析:

  • 首先,如前所述,我们定义一个测试用例数组。每个测试用例是一个对象,包含描述、USDC 余额金额和一个布尔值,指示测试是应该成功还是失败。在这种情况下,我们正在测试一个 ATA 余额不足的情况(预期失败)以及一个 ATA 余额充足的情况(预期成功)。
  • 接下来,我们为 USDC 铸造初始化创建一个测试。我们使用 BanksClientgetAccount 方法获取 USDC 铸造的原始账户信息。然后我们使用 expect 函数检查账户是否存在,以及小数位数是否与预期值匹配。对于每个测试用例,我们创建一个描述测试用例的 describe 块。在 describe 块内部,我们创建一个设置测试环境的 before 块。在创建和发送交易之前,我们使用 setupATA 函数创建一个具有指定余额的 ATA。然后我们使用 createAndProcessTransaction 函数将交易发送到账本状态。
  • 然后我们根据指令是否预期成功或失败,在 describe 块内部运行一系列测试。对于每个测试,我们创建一个描述测试用例的 it 块。在 it 块内部,我们检查交易结果,并对预期行为进行断言。这些测试在结构上与之前编写的测试相似,利用 BanksTransactionResultWithMeta 对象的 resultsmeta.logMessages 属性。

运行测试

在你的终端中输入:

anchor test

你的测试应通过:

18 passing (2s)
  Done in 3.02s.

哇!真快!尽管向集群发送了几笔交易,但我们的测试在几分之一秒内完成。这是使用 Bankrun 的一个巨大优势,因为它使我们能以更高效的方式运行测试!但是那些日志呢?不用担心——如果你不想看到它们,可以轻松删除它们。

打开 Anchor.toml 并将 [scripts] 部分更新为这样的格式:

[scripts]
test = "RUST_LOG= yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
test_debug = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

现在,当你运行 anchor test 时,应该不会看到日志:

  Bankrun Tests
    Time Travel Tests
      When slot is 99 (too early)
         transaction should fail
         should contain specific error details in log
         last log message should indicate failure
      When slot is 100 (at or above threshold)
         transaction should succeed
         last log message should indicate success
         should contain expected log message
         should set new data correctly
    Arbitrary Data Account Tests
      USDC mint initialization
         should have initialized USDC mint
         should have correct decimals
      ATA with insufficient USDC balance
         should have initialized USDC ATA
         should have correct balance in ATA
         should fail to process the transaction
         should contain specific error details in log
         last log message should indicate failure
      ATA with sufficient USDC balance
         should have initialized USDC ATA
         should have correct balance in ATA
         should process the transaction successfully
         last log message should indicate success

  18 passing (442ms)

  Done in 1.19s.

这太棒了!但是我们还添加了一个 test_debug 脚本,如果我们需要调试某些内容,它将显示日志。让我们重新运行测试,这次将使用 test_debug 脚本:

就这样!你的日志被显示出来。这是调试测试并确保它们按预期工作的好方法。

总结

干得好!你现在在工具箱中拥有一些额外的工具,以加速 Solana 程序的测试和开发。

资源

  • Solana Bankrun 文档
  • Solana Bankrun GitHub
  • Anchor Bankrun GitHub
  • Solana Bankrun 教程
  • Solana Bankrun 解释线程 (x.com)

我是 AI 翻译官,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~

版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门