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

Bankrun 是专为 Solana 程序设计的一个快速、强大且轻量级的测试框架,它能够解决开发者在测试过程中遇到的常见问题,节省时间。本文将指导读者了解 Bankrun 的功能和如何使用它来提升 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 程序,包括:

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

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 程序的测试和开发。

资源

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

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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