跳到主要内容

04. 合约功能测试详解

🎯 本章目标

本章将深入讲解如何测试智能合约的各种功能,包括:

  • 代币合约测试
  • 投票合约测试
  • 多签钱包测试
  • 升级合约测试
  • 复杂业务逻辑测试

🪙 代币合约测试详解

1. ERC20 代币基本功能测试

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("ERC20 Token", function () {
let token, owner, user1, user2;

beforeEach(async function () {
[owner, user1, user2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy("MyToken", "MTK");
});

describe("Basic Properties", function () {
it("should have correct name and symbol", async function () {
expect(await token.name()).to.equal("MyToken");
expect(await token.symbol()).to.equal("MTK");
expect(await token.decimals()).to.equal(18);
});

it("should have correct total supply", async function () {
const totalSupply = await token.totalSupply();
expect(totalSupply).to.equal(ethers.parseEther("1000000")); // 100万代币
});

it("should assign initial balance to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(ownerBalance).to.equal(await token.totalSupply());
});
});

describe("Transfer Function", function () {
it("should transfer tokens between accounts", async function () {
const transferAmount = ethers.parseEther("100");
const initialOwnerBalance = await token.balanceOf(owner.address);
const initialUser1Balance = await token.balanceOf(user1.address);

await token.transfer(user1.address, transferAmount);

expect(await token.balanceOf(owner.address))
.to.equal(initialOwnerBalance - transferAmount);
expect(await token.balanceOf(user1.address))
.to.equal(initialUser1Balance + transferAmount);
});

it("should emit Transfer event", async function () {
const transferAmount = ethers.parseEther("100");

await expect(token.transfer(user1.address, transferAmount))
.to.emit(token, "Transfer")
.withArgs(owner.address, user1.address, transferAmount);
});

it("should fail when sender has insufficient balance", async function () {
const largeAmount = ethers.parseEther("10000000"); // 超过总供应量

await expect(
token.transfer(user1.address, largeAmount)
).to.be.revertedWith("ERC20: transfer amount exceeds balance");
});

it("should fail when transferring to zero address", async function () {
await expect(
token.transfer(ethers.ZeroAddress, 100)
).to.be.revertedWith("ERC20: transfer to the zero address");
});
});

describe("Approve and TransferFrom", function () {
it("should approve and transfer from", async function () {
const approveAmount = ethers.parseEther("100");
const transferAmount = ethers.parseEther("50");

// 用户1授权用户2使用代币
await token.connect(user1).approve(user2.address, approveAmount);

// 验证授权额度
expect(await token.allowance(user1.address, user2.address))
.to.equal(approveAmount);

// 用户2从用户1转移代币
await token.connect(user2).transferFrom(user1.address, user2.address, transferAmount);

// 验证余额变化
expect(await token.balanceOf(user1.address))
.to.equal(ethers.parseEther("999900")); // 1000000 - 100
expect(await token.balanceOf(user2.address))
.to.equal(transferAmount);

// 验证授权额度减少
expect(await token.allowance(user1.address, user2.address))
.to.equal(approveAmount - transferAmount);
});

it("should emit Approval event", async function () {
const approveAmount = ethers.parseEther("100");

await expect(token.approve(user1.address, approveAmount))
.to.emit(token, "Approval")
.withArgs(owner.address, user1.address, approveAmount);
});

it("should fail transferFrom when allowance is insufficient", async function () {
const approveAmount = ethers.parseEther("100");
const transferAmount = ethers.parseEther("150");

await token.connect(user1).approve(user2.address, approveAmount);

await expect(
token.connect(user2).transferFrom(user1.address, user2.address, transferAmount)
).to.be.revertedWith("ERC20: insufficient allowance");
});
});

describe("Mint and Burn", function () {
it("should allow owner to mint tokens", async function () {
const mintAmount = ethers.parseEther("1000");
const initialSupply = await token.totalSupply();

await token.mint(user1.address, mintAmount);

expect(await token.totalSupply()).to.equal(initialSupply + mintAmount);
expect(await token.balanceOf(user1.address)).to.equal(mintAmount);
});

it("should allow owner to burn tokens", async function () {
const burnAmount = ethers.parseEther("1000");
const initialSupply = await token.totalSupply();
const initialBalance = await token.balanceOf(owner.address);

await token.burn(burnAmount);

expect(await token.totalSupply()).to.equal(initialSupply - burnAmount);
expect(await token.balanceOf(owner.address)).to.equal(initialBalance - burnAmount);
});

it("should prevent non-owner from minting", async function () {
await expect(
token.connect(user1).mint(user1.address, 1000)
).to.be.revertedWith("Ownable: caller is not the owner");
});
});
});

2. 代币合约边界条件测试

describe("Edge Cases", function () {
it("should handle zero amount transfers", async function () {
const initialBalance = await token.balanceOf(owner.address);

await expect(token.transfer(user1.address, 0)).to.not.be.reverted;

// 余额应该不变
expect(await token.balanceOf(owner.address)).to.equal(initialBalance);
expect(await token.balanceOf(user1.address)).to.equal(0);
});

it("should handle maximum uint256 values", async function () {
const maxAmount = ethers.MaxUint256;

// 测试最大授权额度
await expect(token.approve(user1.address, maxAmount)).to.not.be.reverted;
expect(await token.allowance(owner.address, user1.address)).to.equal(maxAmount);

// 测试最大转账额度
await expect(token.transfer(user1.address, maxAmount)).to.not.be.reverted;
});

it("should handle decimal precision", async function () {
const smallAmount = 1; // 1 wei

await token.transfer(user1.address, smallAmount);
expect(await token.balanceOf(user1.address)).to.equal(smallAmount);
});
});

🗳️ 投票合约测试详解

1. 投票系统核心功能测试

describe("Voting System", function () {
let voting, owner, candidate1, candidate2, voter1, voter2, voter3;

beforeEach(async function () {
[owner, candidate1, candidate2, voter1, voter2, voter3] = await ethers.getSigners();
const Voting = await ethers.getContractFactory("Voting");
voting = await Voting.deploy();

// 设置候选人和选民
await voting.setCandidate(candidate1.address, "25", "John", "image1", "ipfs1");
await voting.setCandidate(candidate2.address, "30", "Jane", "image2", "ipfs2");
await voting.voterRight(voter1.address, "Alice", "image1", "ipfs1");
await voting.voterRight(voter2.address, "Bob", "image2", "ipfs2");
await voting.voterRight(voter3.address, "Charlie", "image3", "ipfs3");
});

describe("Candidate Management", function () {
it("should allow owner to add candidates", async function () {
const candidateCount = await voting.getCandidateLength();
expect(candidateCount).to.equal(2);

const candidateList = await voting.getCandidate();
expect(candidateList).to.include(candidate1.address);
expect(candidateList).to.include(candidate2.address);
});

it("should prevent non-owner from adding candidates", async function () {
const [unauthorized] = await ethers.getSigners();

await expect(
voting.connect(unauthorized).setCandidate(
unauthorized.address, "25", "Unauthorized", "image", "ipfs"
)
).to.be.revertedWith("You have no azuthorization to set Candidate");
});

it("should return correct candidate data", async function () {
const candidateData = await voting.getCandidateData(candidate1.address);

expect(candidateData[0]).to.equal("25"); // age
expect(candidateData[1]).to.equal("John"); // name
expect(candidateData[2]).to.equal(1); // candidateId
expect(candidateData[3]).to.equal("image1"); // image
expect(candidateData[4]).to.equal(0); // voteCount
expect(candidateData[5]).to.equal("ipfs1"); // ipfs
expect(candidateData[6]).to.equal(candidate1.address); // address
});
});

describe("Voter Management", function () {
it("should allow owner to grant voting rights", async function () {
const voterCount = await voting.getVoterLength();
expect(voterCount).to.equal(3);

const voterList = await voting.getVoterList();
expect(voterList).to.include(voter1.address);
});

it("should prevent duplicate voter registration", async function () {
await expect(
voting.voterRight(voter1.address, "Alice Updated", "image2", "ipfs2")
).to.be.reverted; // 应该失败,因为选民已经存在
});

it("should return correct voter data", async function () {
const voterData = await voting.getVoterData(voter1.address);

expect(voterData[0]).to.equal(1); // voter_voterId
expect(voterData[1]).to.equal("Alice"); // voter_name
expect(voterData[2]).to.equal("image1"); // voter_image
expect(voterData[3]).to.equal(voter1.address); // voter_address
expect(voterData[4]).to.equal("ipfs1"); // voter_ipfs
expect(voterData[5]).to.equal(1); // voter_allowed
expect(voterData[6]).to.equal(false); // voter_voted
});
});

describe("Voting Process", function () {
it("should allow authorized voters to vote", async function () {
await expect(
voting.connect(voter1).vote(candidate1.address, 1)
).to.not.be.reverted;

const voter = await voting.voters(voter1.address);
expect(voter.voter_voted).to.equal(true);
expect(voter.voter_vote).to.equal(1);
});

it("should increment candidate vote count", async function () {
const initialVoteCount = (await voting.candidates(candidate1.address)).voteCount;

await voting.connect(voter1).vote(candidate1.address, 1);

const newVoteCount = (await voting.candidates(candidate1.address)).voteCount;
expect(newVoteCount).to.equal(initialVoteCount + 1n);
});

it("should prevent unauthorized users from voting", async function () {
const [unauthorized] = await ethers.getSigners();

await expect(
voting.connect(unauthorized).vote(candidate1.address, 1)
).to.be.revertedWith("You have no right to vote");
});

it("should prevent double voting", async function () {
await voting.connect(voter1).vote(candidate1.address, 1);

await expect(
voting.connect(voter1).vote(candidate2.address, 2)
).to.be.revertedWith("You have already voted");
});

it("should track voted voters", async function () {
await voting.connect(voter1).vote(candidate1.address, 1);
await voting.connect(voter2).vote(candidate2.address, 2);

const votedVotersList = await voting.getVotedVotersList();
expect(votedVotersList).to.have.lengthOf(2);
expect(votedVotersList).to.include(voter1.address);
expect(votedVotersList).to.include(voter2.address);
});
});

describe("Vote Counting", function () {
it("should count votes correctly for multiple candidates", async function () {
// 选民1投票给候选人1
await voting.connect(voter1).vote(candidate1.address, 1);

// 选民2和3投票给候选人2
await voting.connect(voter2).vote(candidate2.address, 2);
await voting.connect(voter3).vote(candidate2.address, 2);

const candidate1Votes = (await voting.candidates(candidate1.address)).voteCount;
const candidate2Votes = (await voting.candidates(candidate2.address)).voteCount;

expect(candidate1Votes).to.equal(1);
expect(candidate2Votes).to.equal(2);
});

it("should handle vote count overflow", async function () {
// 测试大量投票的情况
for (let i = 0; i < 100; i++) {
const [tempVoter] = await ethers.getSigners(i + 10);
await voting.voterRight(tempVoter.address, `Voter${i}`, "image", "ipfs");
await voting.connect(tempVoter).vote(candidate1.address, 1);
}

const finalVoteCount = (await voting.candidates(candidate1.address)).voteCount;
expect(finalVoteCount).to.equal(100);
});
});
});

🔐 多签钱包测试详解

1. 多签钱包基本功能测试

describe("Multi-Sig Wallet", function () {
let wallet, owner1, owner2, owner3, user1;

beforeEach(async function () {
[owner1, owner2, owner3, user1] = await ethers.getSigners();
const owners = [owner1.address, owner2.address, owner3.address];
const required = 2; // 需要2个签名

const MultiSigWallet = await ethers.getContractFactory("MultiSigWallet");
wallet = await MultiSigWallet.deploy(owners, required);
});

describe("Deployment", function () {
it("should set correct owners and required signatures", async function () {
expect(await wallet.required()).to.equal(2);
expect(await wallet.isOwner(owner1.address)).to.be.true;
expect(await wallet.isOwner(owner2.address)).to.be.true;
expect(await wallet.isOwner(owner3.address)).to.be.true;
expect(await wallet.isOwner(user1.address)).to.be.false;
});

it("should not allow zero address as owner", async function () {
const owners = [owner1.address, ethers.ZeroAddress];

await expect(
MultiSigWallet.deploy(owners, 1)
).to.be.revertedWith("Invalid owner");
});

it("should not allow required > owners", async function () {
const owners = [owner1.address, owner2.address];

await expect(
MultiSigWallet.deploy(owners, 3)
).to.be.revertedWith("Invalid required number of owners");
});
});

describe("Transaction Submission", function () {
it("should allow owner to submit transaction", async function () {
const destination = user1.address;
const value = ethers.parseEther("1");
const data = "0x";

await expect(
wallet.connect(owner1).submitTransaction(destination, value, data)
).to.emit(wallet, "Submission")
.withArgs(0); // transactionId
});

it("should prevent non-owner from submitting transaction", async function () {
await expect(
wallet.connect(user1).submitTransaction(user1.address, 0, "0x")
).to.be.revertedWith("Not owner");
});

it("should increment transaction count", async function () {
await wallet.connect(owner1).submitTransaction(user1.address, 0, "0x");
await wallet.connect(owner1).submitTransaction(user1.address, 0, "0x");

expect(await wallet.transactionCount()).to.equal(2);
});
});

describe("Transaction Confirmation", function () {
beforeEach(async function () {
await wallet.connect(owner1).submitTransaction(user1.address, 0, "0x");
});

it("should allow owner to confirm transaction", async function () {
await expect(
wallet.connect(owner1).confirmTransaction(0)
).to.emit(wallet, "Confirmation")
.withArgs(owner1.address, 0);
});

it("should prevent non-owner from confirming", async function () {
await expect(
wallet.connect(user1).confirmTransaction(0)
).to.be.revertedWith("Not owner");
});

it("should prevent double confirmation", async function () {
await wallet.connect(owner1).confirmTransaction(0);

await expect(
wallet.connect(owner1).confirmTransaction(0)
).to.be.revertedWith("Transaction already confirmed");
});

it("should execute transaction when required confirmations met", async function () {
// 需要2个确认
await wallet.connect(owner1).confirmTransaction(0);
await wallet.connect(owner2).confirmTransaction(0);

// 交易应该被执行
expect(await wallet.isConfirmed(0)).to.be.true;
});
});

describe("Transaction Execution", function () {
beforeEach(async function () {
// 提交并确认交易
await wallet.connect(owner1).submitTransaction(user1.address, 0, "0x");
await wallet.connect(owner1).confirmTransaction(0);
await wallet.connect(owner2).confirmTransaction(0);
});

it("should allow owner to execute confirmed transaction", async function () {
await expect(
wallet.connect(owner1).executeTransaction(0)
).to.emit(wallet, "Execution")
.withArgs(0);
});

it("should prevent execution of unconfirmed transaction", async function () {
// 提交新交易但不确认
await wallet.connect(owner1).submitTransaction(user1.address, 0, "0x");

await expect(
wallet.connect(owner1).executeTransaction(1)
).to.be.revertedWith("Transaction not confirmed");
});

it("should prevent double execution", async function () {
await wallet.connect(owner1).executeTransaction(0);

await expect(
wallet.connect(owner1).executeTransaction(0)
).to.be.revertedWith("Transaction already executed");
});
});

describe("Owner Management", function () {
it("should allow owners to add new owner", async function () {
const newOwner = user1.address;

// 需要所有所有者确认
await wallet.connect(owner1).addOwner(newOwner);
await wallet.connect(owner2).addOwner(newOwner);
await wallet.connect(owner3).addOwner(newOwner);

expect(await wallet.isOwner(newOwner)).to.be.true;
});

it("should allow owners to remove owner", async function () {
await wallet.connect(owner1).removeOwner(owner3.address);
await wallet.connect(owner2).removeOwner(owner3.address);

expect(await wallet.isOwner(owner3.address)).to.be.false;
});

it("should prevent removing owner when required > remaining owners", async function () {
// 当前需要2个签名,如果移除1个所有者,剩余2个,仍然可以满足要求
await wallet.connect(owner1).removeOwner(owner3.address);
await wallet.connect(owner2).removeOwner(owner3.address);

// 但如果尝试移除更多所有者,应该失败
await expect(
wallet.connect(owner1).removeOwner(owner2.address)
).to.be.revertedWith("Invalid required number of owners");
});
});
});

🔄 升级合约测试详解

1. 代理合约升级测试

describe("Upgradeable Contract", function () {
let proxy, implementation1, implementation2, owner, user1;

beforeEach(async function () {
[owner, user1] = await ethers.getSigners();

// 部署实现合约
const Implementation1 = await ethers.getContractFactory("Implementation1");
implementation1 = await Implementation1.deploy();

const Implementation2 = await ethers.getContractFactory("Implementation2");
implementation2 = await Implementation2.deploy();

// 部署代理合约
const Proxy = await ethers.getContractFactory("Proxy");
proxy = await Proxy.deploy(implementation1.address);
});

describe("Initial Deployment", function () {
it("should deploy with correct implementation", async function () {
expect(await proxy.implementation()).to.equal(implementation1.address);
});

it("should work with initial implementation", async function () {
const result = await proxy.getValue();
expect(result).to.equal(42);
});
});

describe("Upgrade Process", function () {
it("should allow owner to upgrade implementation", async function () {
await expect(
proxy.connect(owner).upgradeTo(implementation2.address)
).to.emit(proxy, "Upgraded")
.withArgs(implementation2.address);

expect(await proxy.implementation()).to.equal(implementation2.address);
});

it("should prevent non-owner from upgrading", async function () {
await expect(
proxy.connect(user1).upgradeTo(implementation2.address)
).to.be.revertedWith("Ownable: caller is not the owner");
});

it("should work with new implementation", async function () {
await proxy.connect(owner).upgradeTo(implementation2.address);

const result = await proxy.getValue();
expect(result).to.equal(100); // 新实现返回100
});

it("should preserve state after upgrade", async function () {
// 在升级前设置一些状态
await proxy.setValue(123);
expect(await proxy.getValue()).to.equal(123);

// 升级实现
await proxy.connect(owner).upgradeTo(implementation2.address);

// 状态应该保持不变
expect(await proxy.getValue()).to.equal(123);
});
});

describe("Upgrade Safety", function () {
it("should not allow upgrade to invalid address", async function () {
await expect(
proxy.connect(owner).upgradeTo(ethers.ZeroAddress)
).to.be.revertedWith("Invalid implementation");
});

it("should not allow upgrade to same implementation", async function () {
await expect(
proxy.connect(owner).upgradeTo(implementation1.address)
).to.be.revertedWith("Same implementation");
});

it("should validate implementation contract", async function () {
// 部署一个无效的合约
const InvalidContract = await ethers.getContractFactory("InvalidContract");
const invalidContract = await InvalidContract.deploy();

await expect(
proxy.connect(owner).upgradeTo(invalidContract.address)
).to.be.revertedWith("Invalid implementation");
});
});

describe("Rollback Functionality", function () {
it("should allow rollback to previous implementation", async function () {
// 升级到新实现
await proxy.connect(owner).upgradeTo(implementation2.address);
expect(await proxy.implementation()).to.equal(implementation2.address);

// 回滚到旧实现
await proxy.connect(owner).rollback();
expect(await proxy.implementation()).to.equal(implementation1.address);
});

it("should preserve state during rollback", async function () {
// 设置状态
await proxy.setValue(456);

// 升级和回滚
await proxy.connect(owner).upgradeTo(implementation2.address);
await proxy.connect(owner).rollback();

// 状态应该保持不变
expect(await proxy.getValue()).to.equal(456);
});
});
});

🏗️ 复杂业务逻辑测试

1. 借贷协议测试示例

describe("Lending Protocol", function () {
let lending, token, user1, user2, user3;

beforeEach(async function () {
[user1, user2, user3] = await ethers.getSigners();

// 部署代币合约
const Token = await ethers.getContractFactory("TestToken");
token = await Token.deploy("Test Token", "TEST");

// 部署借贷协议
const Lending = await ethers.getContractFactory("LendingProtocol");
lending = await Lending.deploy(token.address);

// 给用户分配代币
await token.mint(user1.address, ethers.parseEther("10000"));
await token.mint(user2.address, ethers.parseEther("10000"));
});

describe("Deposit and Withdraw", function () {
it("should allow users to deposit tokens", async function () {
const depositAmount = ethers.parseEther("1000");

await token.connect(user1).approve(lending.address, depositAmount);
await lending.connect(user1).deposit(depositAmount);

expect(await lending.getDeposit(user1.address)).to.equal(depositAmount);
expect(await token.balanceOf(lending.address)).to.equal(depositAmount);
});

it("should allow users to withdraw tokens", async function () {
const depositAmount = ethers.parseEther("1000");
const withdrawAmount = ethers.parseEther("500");

// 先存款
await token.connect(user1).approve(lending.address, depositAmount);
await lending.connect(user1).deposit(depositAmount);

// 再取款
await lending.connect(user1).withdraw(withdrawAmount);

expect(await lending.getDeposit(user1.address))
.to.equal(depositAmount - withdrawAmount);
expect(await token.balanceOf(user1.address))
.to.equal(ethers.parseEther("9500")); // 10000 - 1000 + 500
});

it("should prevent withdrawal exceeding deposit", async function () {
const depositAmount = ethers.parseEther("1000");
const withdrawAmount = ethers.parseEther("1500");

await token.connect(user1).approve(lending.address, depositAmount);
await lending.connect(user1).deposit(depositAmount);

await expect(
lending.connect(user1).withdraw(withdrawAmount)
).to.be.revertedWith("Insufficient deposit");
});
});

describe("Borrowing", function () {
beforeEach(async function () {
// 用户1和2存款作为流动性
const depositAmount = ethers.parseEther("5000");
await token.connect(user1).approve(lending.address, depositAmount);
await token.connect(user2).approve(lending.address, depositAmount);
await lending.connect(user1).deposit(depositAmount);
await lending.connect(user2).deposit(depositAmount);
});

it("should allow borrowing with sufficient collateral", async function () {
const borrowAmount = ethers.parseEther("1000");
const collateralAmount = ethers.parseEther("2000");

// 用户3提供抵押品
await token.connect(user3).approve(lending.address, collateralAmount);
await lending.connect(user3).provideCollateral(collateralAmount);

// 借款
await lending.connect(user3).borrow(borrowAmount);

expect(await lending.getBorrow(user3.address)).to.equal(borrowAmount);
expect(await lending.getCollateral(user3.address)).to.equal(collateralAmount);
});

it("should prevent borrowing without sufficient collateral", async function () {
const borrowAmount = ethers.parseEther("1000");
const collateralAmount = ethers.parseEther("500"); // 不足的抵押品

await token.connect(user3).approve(lending.address, collateralAmount);
await lending.connect(user3).provideCollateral(collateralAmount);

await expect(
lending.connect(user3).borrow(borrowAmount)
).to.be.revertedWith("Insufficient collateral");
});

it("should calculate interest correctly", async function () {
const borrowAmount = ethers.parseEther("1000");
const collateralAmount = ethers.parseEther("2000");

await token.connect(user3).approve(lending.address, collateralAmount);
await lending.connect(user3).provideCollateral(collateralAmount);
await lending.connect(user3).borrow(borrowAmount);

// 等待一段时间让利息累积
await ethers.provider.send("evm_increaseTime", [86400]); // 1天
await ethers.provider.send("evm_mine");

const interest = await lending.calculateInterest(user3.address);
expect(interest).to.be.greaterThan(0);
});
});

describe("Liquidation", function () {
beforeEach(async function () {
// 设置场景:用户3借款,抵押品价值下降
const depositAmount = ethers.parseEther("5000");
await token.connect(user1).approve(lending.address, depositAmount);
await token.connect(user2).approve(lending.address, depositAmount);
await lending.connect(user1).deposit(depositAmount);
await lending.connect(user2).deposit(depositAmount);

const borrowAmount = ethers.parseEther("1000");
const collateralAmount = ethers.parseEther("2000");
await token.connect(user3).approve(lending.address, collateralAmount);
await lending.connect(user3).provideCollateral(collateralAmount);
await lending.connect(user3).borrow(borrowAmount);
});

it("should allow liquidation when collateral ratio is too low", async function () {
// 模拟抵押品价值下降(通过价格预言机)
await lending.setCollateralPrice(ethers.parseEther("0.5")); // 价格减半

// 检查是否可以被清算
expect(await lending.canLiquidate(user3.address)).to.be.true;

// 执行清算
const liquidateAmount = ethers.parseEther("500");
await lending.connect(user1).liquidate(user3.address, liquidateAmount);

// 验证清算结果
expect(await lending.getBorrow(user3.address))
.to.equal(ethers.parseEther("500")); // 借款减少
});

it("should prevent liquidation when collateral ratio is sufficient", async function () {
// 抵押品价格正常
await lending.setCollateralPrice(ethers.parseEther("1"));

expect(await lending.canLiquidate(user3.address)).to.be.false;

await expect(
lending.connect(user1).liquidate(user3.address, ethers.parseEther("100"))
).to.be.revertedWith("Cannot liquidate");
});
});
});

📚 总结

本章我们深入学习了:

  • 代币合约测试: 包括基本功能、权限控制、边界条件等
  • 投票系统测试: 候选人管理、选民管理、投票过程、计票等
  • 多签钱包测试: 所有者管理、交易提交、确认、执行等
  • 升级合约测试: 实现升级、状态保持、回滚等
  • 复杂业务逻辑测试: 借贷协议、清算机制等

关键要点:

  1. 全面覆盖: 测试所有公共函数和状态变量
  2. 边界条件: 测试零值、最大值、无效输入等
  3. 权限控制: 测试访问控制和权限验证
  4. 状态变化: 验证合约状态正确更新
  5. 错误处理: 测试各种异常情况
  6. 事件验证: 确保事件正确触发和参数正确

在下一章中,我们将学习高级测试技巧,包括测试优化、模拟外部依赖等。


🧠 练习

  1. 为你的投票合约添加更多测试用例
  2. 创建一个简单的代币合约并编写完整测试
  3. 测试合约的边界条件和错误情况
  4. 练习测试事件触发和参数验证

🔗 相关链接

📢 Share this article