DApp开发 ✅ 验证通过
单元测试是基础,覆盖所有函数的输入输出;集成测试验证多合约之间的交互逻辑;Fuzz测试使用随机输入发现边界条件bug。Solidity测试还包括不变量测试(Invariant Testing),验证系统状态始终满足某些约束条件。
const { expect } = require("chai");
const { loadFixture, time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
describe("Token", function () {
async function deployFixture() {
const [owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy("Test","TST",1000000);
return { token, owner, addr1, addr2 };
}
it("should transfer", async function () {
const { token, owner, addr1 } = await loadFixture(deployFixture);
await token.transfer(addr1.address, 100);
expect(await token.balanceOf(addr1.address)).to.equal(100);
});
it("should fail insufficient balance", async function () {
const { token, addr1, owner } = await loadFixture(deployFixture);
await expect(token.connect(addr1).transfer(owner.address, 1))
.to.be.revertedWith("Insufficient balance");
});
it("should emit Transfer event", async function () {
const { token, owner, addr1 } = await loadFixture(deployFixture);
await expect(token.transfer(addr1.address, 100))
.to.emit(token, "Transfer")
.withArgs(owner.address, addr1.address, 100);
});
it("should respect lock period", async function () {
const { token, addr1 } = await loadFixture(deployFixture);
await token.lock(addr1.address, 100, 3600);
await expect(token.connect(addr1).withdraw(100))
.to.be.revertedWith("Locked");
await time.increase(3601);
await expect(token.connect(addr1).withdraw(100)).to.not.be.reverted;
});
});
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "hardhat/console.sol";
contract DebugExample {
mapping(address => uint256) public balances;
function deposit() public payable {
console.log("Deposit from:", msg.sender);
console.log("Old balance:", balances[msg.sender]);
balances[msg.sender] += msg.value;
console.log("New balance:", balances[msg.sender]);
// ✅ 只在Hardhat网络输出,主网自动移除
}
}
// ✅ 验证通过
测试金字塔哪个最多?
loadFixture?
console.sol主网?
time.increase?
to.be.revertedWith?
const { expect } = require("chai");
describe("Token Fuzz", function () {
it("should never allow balance below zero", async function () {
const [owner, addr1] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy("T","T",1000000);
// 随机金额测试
for (let i = 0; i < 50; i++) {
const amount = Math.floor(Math.random() * 1000);
const balance = await token.balanceOf(owner.address);
if (amount > balance) {
await expect(token.transfer(addr1.address, amount))
.to.be.reverted;
}
}
});
});
it("should report gas usage", async function () {
const { token, addr1 } = await loadFixture(deployFixture);
const tx = await token.transfer(addr1.address, 100);
const receipt = await tx.wait();
console.log("Transfer gas:", receipt.gasUsed.toString());
expect(receipt.gasUsed).to.be.below(60000);
});
// Foundry: Solidity原生测试框架(比Hardhat快10x)
// 安装: curl -L https://foundry.paradigm.xyz | bash && foundryup
// test/Counter.t.sol
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter public counter;
address public user = makeAddr("user");
function setUp() public {
counter = new Counter();
}
function test_Increment() public {
counter.increment();
assertEq(counter.getCount(), 1);
}
function testFuzz_Add(uint256 x) public {
// ✅ Foundry原生fuzz: 自动用随机值测试
counter.add(x);
assertEq(counter.getCount(), x);
}
function test_EmitEvent() public {
vm.expectEmit(true, true, false, true);
emit CountIncremented(1, address(this));
counter.increment();
}
}
// 不变量: 系统必须始终满足的条件
// 例: Token总供给 = 所有余额之和
// Foundry不变量测试
contract InvariantTest is Test {
TokenHandler handler;
Token token;
function setUp() public {
token = new Token("T","T",0);
handler = new TokenHandler(token);
targetContract(address(handler));
}
// ✅ 不变量: 总供给必须等于所有余额之和
function invariant_totalSupplyEqualsBalances() public {
uint256 total = token.totalSupply();
uint256 sum = token.balanceOf(address(handler));
assertEq(total, sum);
}
}
// .github/workflows/test.yml
name: Hardhat Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx hardhat compile
- run: npx hardhat test
- run: npx hardhat coverage
- uses: codecov/codecov-action@v3
with: { files: './coverage/lcov.info' }
| 覆盖率类型 | 目标 | 说明 |
|---|---|---|
| 行覆盖率 | >90% | 每行代码至少执行一次 |
| 分支覆盖率 | >85% | 每个if/else分支都测试 |
| 函数覆盖率 | 100% | 每个函数都调用 |
| 修饰符覆盖率 | 100% | 每个modifier都验证 |
本课全面覆盖了Solidity测试策略、Hardhat测试工具、console.log调试、Fuzz测试、Gas优化测试和常见测试反模式。
你已掌握合约测试——策略/console/覆盖率!
✅ 测试金字塔 ✅ Hardhat测试 ✅ console调试 ✅ 覆盖率