练习3:测试覆盖率
我知道,你想直接部署合约和前端,并立刻就开始在测试网上进行测试,但是......我们需要确定一切都按预期工作,而不需要在前端用户界面(UI) 上进行 monkey 测试。
因此,在文章的下一部分,我将介绍一些开发人员应该做的事情:测试合约逻辑!
Waffle是一个用于编写和测试智能合约的库,它与 ethers-js 配合得非常默契。
Waffle 有很多有帮助的工具。waffle 中的测试是用Mocha和Chai一起编写的。你可以使用不同的测试环境,但 Waffle 的匹配器(matcher)只能在
chai
下工作。
我们将使用Chai 匹配器来验证我们所期望的条件是否已经满足。
在写完所有的测试用例后,你只需要输入yarn test
,就会自动针对你的合约进行测试。
我不会解释如何使用这个库(你可以简单地看一下下面的代码来了解),我将专注于应该测试什么。
我们的合约已经实现了一些逻辑:
mapping(address => uint256) public balances
保存用户余额uint256 public constant threshold = 1 ether
。uint256 public deadline = block.timestamp + 120 seconds
。completed
并且 deadline
还没有到,用户可以调用stake()
函数completed
并且 deadline
还没有到,用户可以调用 execute
方法。deadline
并且外部合约不是 completed
,用户可以撤回资金。timeLeft()
返回剩余的秒数,直到时间到deadline
,之后它应该总是返回0
PS: 这是我个人的测试方法,如果你有建议,请在 Twitter 上找我!
我写测试的时候,习惯用一个独立的函数并且覆盖所有边缘情况。试试写一写测试用例来回答下面的问题:
还记得我们说过吗,为了正确模拟 timeLeft()
,我们必须创建交易或从水龙头(Faucet)获取资金(这也是一种交易)。好吧,为了解决这个问题,我写了一个小程序(你可以直接复制到其他项目中)。
当你调用increaseWorldTimeInSeconds(10, true)
时,EVM 内部时间戳会比当前时间快进10秒。之后,如果指定出块,它还会挖一个块来创建一个交易。
下次合约被调用时,timeLeft()
应该被更新。
我们先看这一部分测试,然后我将发布整段代码,我只解释其中一些特定的代码。这段代码涵盖了 execute()
函数:
describe('Test execute() method', () => {
it('execute reverted because stake amount not reached threshold', async () => {
await expect(stakerContract.connect(addr1).execute()).to.be.revertedWith('Threshold not reached');
});
it('execute reverted because external contract already completed', async () => {
const amount = ethers.utils.parseEther('1');
await stakerContract.connect(addr1).stake({
value: amount,
});
await stakerContract.connect(addr1).execute();
await expect(stakerContract.connect(addr1).execute()).to.be.revertedWith('staking process already completed');
});
it('execute reverted because deadline is reached', async () => {
// reach the deadline
await increaseWorldTimeInSeconds(180, true);
await expect(stakerContract.connect(addr1).execute()).to.be.revertedWith('Deadline is already reached');
});
it('external contract sucessfully completed', async () => {
const amount = ethers.utils.parseEther('1');
await stakerContract.connect(addr1).stake({
value: amount,
});
await stakerContract.connect(addr1).execute();
// check that the external contract is completed
const completed = await exampleExternalContract.completed();
expect(completed).to.equal(true);
// check that the external contract has the staked amount in it's balance
const externalContractBalance = await ethers.provider.getBalance(exampleExternalContract.address);
expect(externalContractBalance).to.equal(amount);
// check that the staking contract has 0 balance
const contractBalance = await ethers.provider.getBalance(stakerContract.address);
expect(contractBalance).to.equal(0);
});
});
execute()
函数,它将撤销交易并返回适当的错误信息。execute()
函数,质押已经完成,交易应该被撤销,防止再次调用。execute()
函数。交易应该被撤销,因为只能在时间到 deadline 之前调用execute()
函数。execute()
函数不会回退,并且所有都如预期一样。在函数调用外部合约后,completed
变量应该是true
,外部合约balance
应该等于用户的质押金额,我们的合约余额应该等于0
(已经将所有的余额转移到外部合约中)。如果一切正常,运行yarn test
应该会有这样的输出:
下面我们来看看整个测试代码:
const {ethers} = require('hardhat');
const {use, expect} = require('chai');
const {solidity} = require('ethereum-waffle');
use(solidity);
// Utilities methods
const increaseWorldTimeInSeconds = async (seconds, mine = false) => {
await ethers.provider.send('evm_increaseTime', [seconds]);
if (mine) {
await ethers.provider.send('evm_mine', []);
}
};
describe('Staker dApp', () => {
let owner;
let addr1;
let addr2;
let addrs;
let stakerContract;
let exampleExternalContract;
let ExampleExternalContractFactory;
beforeEach(async () => {
// Deploy ExampleExternalContract contract
ExampleExternalContractFactory = await ethers.getContractFactory('ExampleExternalContract');
exampleExternalContract = await ExampleExternalContractFactory.deploy();
// Deploy Staker Contract
const StakerContract = await ethers.getContractFactory('Staker');
stakerContract = await StakerContract.deploy(examp...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!