Bankrun是什么以及如何使用它来增强Solana本地开发?

本文介绍了Bankrun这一Solana程序测试框架,它通过提供快速验证、时间旅行和任意账户数据等功能,优化了Solana程序的测试流程。文章详细阐述了如何使用Bankrun创建一个新的Anchor程序,并编写测试来验证程序的功能,包括时间旅行和任意数据账户的测试用例。

Bankrun 已弃用

截至 2025 年 3 月,Bankrun 已被弃用,不再维护。本指南仅用于历史教育目的。

对于现代替代方案,请查看如何使用 LiteSVM 测试 Solana 程序指南来测试 Solana 程序。

概述

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

你将做什么

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

你需要什么

依赖 版本
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 程序,包括:

  • 更快的测试验证
  • “时间旅行”以修改区块链的状态
  • 任意账户数据(这对于利用主网上的 token mint 和其他相关账户很有帮助)

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 Token Program。

接下来,让我们将 SPL token 程序添加到我们的 Anchor Program 中。导航到 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 替换为你自己的程序 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 字段设置为当前 slot,并将 new_data 字段设置为签署交易的 new_data 账户的公钥。该指令检查当前 slot 是否大于或等于 MINIMUM_SLOT 常量。如果当前 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 token 中的小数位数。
  • USDC_MINT_ADDRESS: 这是 USDC token mint 的地址。
  • 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 函数启动测试时创建)。然后,它创建一个新交易,将提供的指令添加到其中,使用提供的 payer 对其进行签名,并使用 BanksClienttryProcessTransaction 函数处理它。此函数对于测试非常方便,因为它返回 BanksTransactionResultWithMeta,其中包含交易日志、返回数据、使用的计算单元以及(如果适用)错误。
  • setupATA 接受一个 ProgramTestContext,它实际上是 BanksClient 的扩展,其中包括一些额外的功能,包括 setAccount 函数,该函数允许我们在上下文中设置一个账户。该函数会将提供的账户数据编码到缓冲区中,为提供的所有者和 mint 创建一个关联 token 账户 (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 token mint 的公钥。
  • context: 这是 ProgramTestContext 的一个实例,它是 BanksClient 的一个包装器,其中包括额外的功能。这是使用 startAnchor 函数启动的。请注意,我们正在传入 usdcAccount。这将使用初始化的 USDC mint 账户启动我们的测试环境(我们将编写一个测试来验证这一点)。
  • client: 这是将用于与账本状态交互的 BanksClient
  • payer: 这是将用于签署交易的 payer。
  • provider: 这是 BankrunProvider(Anchor Provider 的一个实现)的一个实例,它将包括额外的上下文和功能。
  • program: 这是 anchor Program<BankrunTest> 的一个实例,它将像任何其他 anchor 测试套件一样使用。

时间旅行测试

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

  1. 使用 provider.context.warpToSlot 函数(或 warpToEpoch 用于 epoch),或
  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());
          });
        }
      });
    });
  });

让我们分解一下测试:

  • 首先,我们定义一个测试用例数组。每个测试用例都是一个对象,其中包含一个描述、slot 编号和一个布尔值,指示测试是否应该成功或失败。在这种情况下,我们正在测试一个当前 slot 小于 MINIMUM_SLOT 常量(预计会失败)的情况,以及另一种当前 slot 大于或等于 MINIMUM_SLOT 常量(预计会成功)的情况。 对于每个测试用例,我们创建一个 describe 块来描述测试用例。在 describe 块中,我们创建一个 before 块来设置测试环境。在创建和发送交易之前,我们使用 provider.context.warpToSlot 函数“时间旅行”到指定的 slot。 然后,我们根据指令是否应该成功或失败,在 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 账户的余额是否大于或等于 TOKEN_MINIMUM_BALANCE 常量。如果你还记得,我们的 Anchor 程序要求 token mint 必须是 USDC mint。运行此测试通常需要使用假的 USDC token,但 Bankrun 允许我们使用任意账户,这意味着我们可以将 token 账户信息写入我们想要的任何账户。事实上,我们已经在我们的 startAnchor 函数中这样做了——通过使用我们从 Solana 主网提取的账户信息来初始化 usdcMint 账户。让我们 (1) 编写一个测试以确保 USDC mint 已正确初始化,以及 (2) 编写一组测试来验证具有足够余额的 ATA 是否已初始化。在你的“Bankrun Tests”描述块中,“Time Travel Tests”块之后,添加以下“Arbitrary Data Account Tests”描述块:

  describe("Arbitrary Data Account Tests", () => {
    const testCases = [\
      { desc: "insufficient", amount: MINIMUM_USDC_BALANCE - 1_000_000, shouldSucceed: false },\
      { desc: "sufficient", amount: MINIMUM_USDC_BALANCE, shouldSucceed: true },\
    ];

    describe("USDC mint initialization", () => {
      let rawAccount: AccountInfoBytes;

      before(async () => {
        rawAccount = await client.getAccount(usdcMint);
      });

      it("should have initialized USDC mint", () => {
        expect(rawAccount).to.exist;
      });

      it("should have correct decimals", () => {
        const mintInfo = MintLayout.decode(rawAccount.data);
        expect(mintInfo.decimals).to.equal(USDC_DECIMALS);
      });
    });

    testCases.forEach(({ desc, amount, shouldSucceed }) => {
      describe(`ATA with ${desc} USDC balance`, () => {
        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("should have initialized USDC ATA", async () => {
          const rawAccount = await client.getAccount(ata);
          expect(rawAccount).to.exist;
        });

        it("should have correct balance in ATA", async () => {
          const accountInfo = await client.getAccount(ata);
          const tokenAccountInfo = AccountLayout.decode(accountInfo.data);
          expect(tokenAccountInfo.amount).to.equal(BigInt(amount));
        });

        if (shouldSucceed) {
          it("should process the transaction successfully", () => {
            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');
          });
        } else {
          it("should fail to process the transaction", () => {
            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('InsufficientTokenBalance') &&
              log.includes('6001') &&
              log.includes('Error Message: Insufficient token balance.')
            );
            expect(errorLog).to.exist;
          });

          it("last log message should indicate failure", () => {
            expect(txResult.meta.logMessages[txResult.meta.logMessages.length - 1]).to.include('failed');
          });
        }
      });
    });
  });

让我们分解一下测试:

  • 首先,和之前一样,我们定义一个测试用例数组。每个测试用例都是一个对象,其中包含一个描述、USDC 余额和指示测试是否应该成功或失败的布尔值。在这种情况下,我们正在测试一个 ATA 余额不足的情况(预计会失败),以及另一种 ATA 余额充足的情况(预计会成功)。
  • 接下来,我们为 USDC mint 初始化创建一个测试。我们使用 BanksClientgetAccount 方法来获取 USDC mint 的原始账户信息。然后,我们使用 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 脚本:


anchor run test_debug
```这就是你所拥有的!你的日志已显示。这是调试你的测试并确保它们按预期工作的一个好方法。

## 总结

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

### 资源

- [Solana Bankrun 文档](https://kevinheavey.github.io/solana-bankrun/api)
- [Solana Bankrun GitHub](https://github.com/kevinheavey/solana-bankrun)
- [Anchor Bankrun GitHub](https://github.com/kevinheavey/anchor-bankrun)
- [Solana Bankrun 教程](https://kevinheavey.github.io/solana-bankrun/tutorial)
- [Solana Bankrun 解释推文 (x.com)](https://x.com/dj_d_sol/status/1690690600121536513)

### 让我们联系!

我们很乐意听听你正在构建和测试的内容。通过 [Twitter](https://twitter.com/Quicknode) 或 [Discord](https://discord.gg/quicknode) 向我们发送你的经验、问题或反馈。

#### 我们 ❤️ 反馈!

如果你对新主题有任何反馈或要求,请[告诉我们](https://airtable.com/shrKKKP7O1Uw3ZcUB?prefill_Guide+Name=What%20is%20Bankrun%20and%20How%20to%20Use%20it%20to%20Enhance%20Solana%20Local%20Development%3F)。我们很乐意听取你的意见。

>- 原文链接: [quicknode.com/guides/sol...](https://www.quicknode.com/guides/solana-development/legacy/bankrun)
>- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。