能否将Mocha Hardhat测试脚本中的合约部署逻辑复用至其他测试脚本?
方案合理性判断
这个方案完全合理,是Hardhat技术栈下优化测试效率的标准方案。它既解决了多测试文件重复部署合约的冗余耗时问题(合约量越大的项目优化效果越明显,通常可降低70%以上的测试执行耗时),又通过状态重置机制保证了不同测试文件、不同测试用例之间的状态隔离,不会出现跨用例的状态污染,完全符合测试的独立性要求。
行业实践情况
该方案已经是Hardhat官方推荐的测试优化手段,大量头部Web3项目(包括Uniswap、AAVE等成熟DeFi协议)的测试套件都采用了类似实现,生态成熟度很高,相关问题的踩坑和解决方案都很完善。
具体实现思路
核心原理是利用EVM快照+回滚机制替代重复的合约部署逻辑,你可以按照以下步骤实现:
- 第一步:抽离公共部署逻辑为全局fixture
把所有测试文件通用的合约部署逻辑封装为独立的异步函数,统一存放在公共目录(比如test/fixtures/),函数返回所有测试需要用到的合约实例、签名账号、配置参数等对象。示例代码如下:// test/fixtures/core.js const { ethers } = require("hardhat"); async function deployCoreFixtures() { const [owner, ordinaryUser, adminUser] = await ethers.getSigners(); // 部署所有通用核心合约 const ERC20 = await ethers.getContractFactory("MyERC20"); const erc20 = await ERC20.deploy(ethers.utils.parseEther("1000000")); await erc20.deployed(); const MinePool = await ethers.getContractFactory("MinePool"); const minePool = await MinePool.deploy(erc20.address); await minePool.deployed(); // 抛出所有测试所需对象 return { owner, ordinaryUser, adminUser, erc20, minePool }; } module.exports = { deployCoreFixtures }; - 第二步:在测试文件中复用fixture
直接使用Waffle提供的loadFixture方法加载你封装的全局fixture即可。loadFixture底层已经实现了快照缓存逻辑:第一次调用时会执行完整部署并拍摄EVM快照,后续所有调用(不管是同一个测试文件的不同用例,还是不同测试文件的调用)都会直接回滚到快照状态,不会重复执行部署逻辑。示例代码如下:// test/minePool.test.js const { expect } = require("chai"); const { loadFixture } = require("@nomiclabs/hardhat-waffle"); const { deployCoreFixtures } = require("./fixtures/core"); describe("矿池合约测试", function () { it("测试正常质押逻辑", async function () { // 加载全局fixture,后续用例调用会直接回滚快照 const { minePool, ordinaryUser, erc20 } = await loadFixture(deployCoreFixtures); // 后续测试逻辑 await erc20.connect(ordinaryUser).approve(minePool.address, ethers.utils.parseEther("100")); await minePool.connect(ordinaryUser).stake(ethers.utils.parseEther("100")); expect(await minePool.userStakeBalance(ordinaryUser.address)).to.equal(ethers.utils.parseEther("100")); }); it("测试正常赎回逻辑", async function () { const { minePool, ordinaryUser, erc20 } = await loadFixture(deployCoreFixtures); // 该用例拿到的合约状态和上一个用例完全独立,不会受到上一个用例操作的影响 await erc20.connect(ordinaryUser).approve(minePool.address, ethers.utils.parseEther("100")); await minePool.connect(ordinaryUser).stake(ethers.utils.parseEther("100")); await minePool.connect(ordinaryUser).withdraw(ethers.utils.parseEther("100")); expect(await minePool.userStakeBalance(ordinaryUser.address)).to.equal(0); }); }) - 可选优化:如果你的测试集很大,还可以基于Mocha的全局钩子实现全局快照,所有测试文件共用同一个快照,进一步降低重复开销。如果部分测试文件需要额外部署定制合约,也可以在全局fixture的基础上扩展二级fixture,同样可以用
loadFixture做缓存。
内容的提问来源于stack exchange,提问作者Juan Ignacio Pérez Sacristán
相关产品推荐
相关产品推荐

