
智能合约的条款无法更改。 因此,无论何时创建智能合约,都需要确保其正常运行。 测试是在不同情况下测试合同的安全方法。 在本教程中,您将学习采取哪些步骤。
我会告诉:
- 如何准备测试环境。
- 如何编写JavaScript测试并在Truffle中执行它们。
本教程面向刚开始在以太坊上开发和测试智能合约的初学者。 要理解本教程,您必须至少具有
JavaScript和
Solidity的基础知识。
1.如何准备测试环境
有许多方法可以测试智能合约,但是
Truffle是使用
JavaScript的最受欢迎的测试编写工具。 在
Truffle上,您可以编写单元测试
,也可以使用生产环境中的实际参数进行完全集成测试。
首先,您需要从
官方站点安装最新版本的Node.js。
然后打开一个终端并使用以下命令安装
Truffle :
npm install -g truffle
安装
Truffle之后 ,无需关闭终端,请创建
Funding目录:
mkdir Funding
接下来,使用以下命令转到目录:
cd Funding
要初始化目录,请运行以下命令:
truffle init
执行该命令后,将在
Funding目录中创建以下文件夹和文件:

此外,我们将处理每个目录。 同时,我们将继续准备测试环境。
要运行测试,您需要Javascript库。
Mocha是一个包含用于测试的常用功能(包括
describe和
it)的库 。
Chai是一个支持多种检查功能的库。 检查结果有不同的“样式”,Chai为我们提供了这一机会。 在本教程中,我们将使用
Should 。
默认情况下,Mocha是Truffle的一部分,我们可以轻松使用库函数。 柴必须手动安装。 为此,请使用终端,并在项目的根目录中运行命令:
npm install chai
我还安装了
chai-bignumber库以比较任意精度的数字:
npm install --save-dev chai-bignumber
测试环境已准备就绪。 现在,您可以开始开发智能合约并对其进行测试。
2.如何编写JavaScript测试并在松露中执行它们
要开发测试,您需要一个智能合约。 我们将制定合同,使您可以收集捐款,设置特定金额以实现收集和提取资金。 如果有人捐出更多的钱,则已累计的金额和需要收取的金额之间的差额将退还给他。
转到
资金->合同目录。 在“
资金/合同”中,创建扩展名为
.sol的
Funding.sol文件-这将是用于测试的智能合同。
在
Funding.sol中,添加以下代码:
pragma solidity 0.4.24; contract Funding { uint public raised; uint public goal; address public owner; event Donated(uint donation); event Withdrew(uint amount); modifier onlyOwner() { require(owner == msg.sender); _; } modifier isFinished() { require(isFunded()); _; } modifier notFinished() { require(!isFunded()); _; } constructor (uint _goal) public { owner = msg.sender; goal = _goal; } function isFunded() public view returns (bool) { return raised >= goal; } function donate() public payable notFinished { uint refund; raised += msg.value; if (raised > goal) { refund = raised - goal; raised -= refund; msg.sender.transfer(refund); } emit Donated(msg.value); } function withdraw() public onlyOwner isFinished { uint amount = address(this).balance; owner.transfer(amount); emit Withdrew(amount); } }
合同准备好了。 我们将通过迁移来部署智能合约。
迁移是
JavaScript文件,可帮助您在以太坊网络上部署合同。 这是部署合同的主要方法。
转到
Funding- > migrations目录并创建
2_funding.js文件,并向其中添加以下代码:
const Funding = artifacts.require("./Funding.sol"); const ETHERS = 10**18; const GOAL = 20 * ETHERS; module.exports = function(deployer) { deployer.deploy(Funding, GOAL); };
要运行测试,您需要使用
松露测试命令。 在终端中,转到在准备测试环境期间创建的
Funding目录的根目录,然后输入:
truffle test
如果终端中显示以下输出,则说明一切正确:

现在让我们开始编写测试。
所有者验证
转到
Funding- > test目录,并创建扩展名为
.js的
test_funding.js文件。 这是将写入测试的文件。
将以下代码添加到
test_funding.js文件:
const Funding = artifacts.require("Funding"); require("chai").use(require("chai-bignumber")(web3.BigNumber)).should(); contract("Funding", function([account, firstDonator, secondDonator]) { const ETHERS = 10**18; const GAS_PRICE = 10**6; let fundingContract = null; it("should check the owner is valid", async () => { fundingContract = await Funding.deployed(); const owner = await fundingContract.owner.call() owner.should.be.bignumber.equal(account); });
在测试中,我们验证“
资助合同”是否存储了部署合同的所有者的地址。 在我们的例子中,
account是数组的第一个元素。
松露最多可使用十个地址,在测试中,我们只需要三个地址。
接受捐赠并结束募捐活动
在本节中,我们将检查:
- 捐款的接受程度和金额。
- 是否已达到一定数量的捐款。
- 如果您捐赠的钱超过了需要筹集的资金,将会发生什么。
- 捐款总额。
- 如果已收集到所需的金额,我可以继续筹集资金吗?
我们将编写并分析测试:
. . . . . . . . . . . . . . . . . . . . . . const ETHERS = 10**18; const GAS_PRICE = 10**6; let fundingContract = null; let txEvent; function findEvent(logs, eventName) { let result = null; for (let log of logs) { if (log.event === eventName) { result = log; break; } } return result; }; it("should accept donations from the donator #1", async () => { const bFirstDonator= web3.eth.getBalance(firstDonator); const donate = await fundingContract.donate({ from: firstDonator, value: 5 * ETHERS, gasPrice: GAS_PRICE }); txEvent = findEvent(donate.logs, "Donated"); txEvent.args.donation.should.be.bignumber.equal(5 * ETHERS); const difference = bFirstDonator.sub(web3.eth.getBalance(firstDonator)).sub(new web3.BigNumber(donate.receipt.gasUsed * GAS_PRICE)); difference.should.be.bignumber.equal(5 * ETHERS); });
在解析测试之前,我要注意两点:
- 为了搜索事件(事件)并检查其参数,编写了一个小的findEvent函数。
- 为方便测试和计算,已设置了气体的固有值(恒定GAS_PRICE)。
现在让我们分析一下测试。 在测试中,我们检查了:
- 我们可以通过调用donate()方法接受捐赠;
- 正确指示了向我们捐赠的金额;
- 那笔捐款,余额减少了捐款额。
it("should check if donation is not completed", async () => { const isFunded = await fundingContract.isFunded(); isFunded.should.be.equal(false); });
在此测试中,我们检查了募捐活动尚未完成。
it("should not allow to withdraw the fund until the required amount has been collected", async () => { let isCaught = false; try { await fundingContract.withdraw({ gasPrice: GAS_PRICE }); } catch (err) { isCaught = true; } isCaught.should.be.equal(true); });
在测试中,我们检查了直到收集到所需的金额后才能提取资金。
it("should accept donations from the donator #2", async () => { const bSecondDonator= web3.eth.getBalance(secondDonator); const donate = await fundingContract.donate({ from: secondDonator, value: 20 * ETHERS, gasPrice: GAS_PRICE }); txEvent = findEvent(donate.logs, "Donated"); txEvent.args.donation.should.be.bignumber.equal(20 * ETHERS); const difference = bSecondDonator.sub(web3.eth.getBalance(secondDonator)).sub(new web3.BigNumber(donate.receipt.gasUsed * GAS_PRICE)); difference.should.be.bignumber.equal(15 * ETHERS); });
在测试中,我们检查了您是否捐赠了很多钱,
donate()方法计算并把资金退还给捐赠超过必要数量的人。 此金额是累计金额和您要收取的金额之间的差额。
it("should check if the donation is completed", async () => { const notFunded = await fundingContract.isFunded(); notFunded.should.be.equal(true); }); it("should check if donated amount of money is correct", async () => { const raised = await fundingContract.raised.call(); raised.should.be.bignumber.equal(20 * ETHERS); }); it("should not accept donations if the fundraising is completed", async () => { let isCaught = false; try { await fundingContract.donate({ from: firstDonator, value: 10 * ETHERS }); } catch (err) { isCaught = true; } isCaught.should.be.equal(true); });
在这三个测试中,我们检查了:
- 筹款已经完成;
- 捐款金额是否正确;
- 由于筹款已经完成,因此没有其他人可以捐赠。
提款
在本教程的上一部分中,我们收集了所需的数量,现在可以显示它:
. . . . . . . . . . . . . . . . . . . . . . it("should allow the owner to withdraw the fund", async () => { const bAccount = web3.eth.getBalance(account); const withdraw = await fundingContract.withdraw({ gasPrice: GAS_PRICE }); txEvent = findEvent(withdraw.logs, "Withdrew"); txEvent.args.amount.should.be.bignumber.equal(20 * ETHERS); const difference = web3.eth.getBalance(account).sub(bAccount); difference.should.be.bignumber.equal(await fundingContract.raised.call() - withdraw.receipt.gasUsed * GAS_PRICE); });
通过调用
withdraw()函数,我们从案例
帐户中提取了智能合约所有者的资金。 然后,我们检查了我们是否确实提取了所需的金额。 为此,将取款前后的余额差额记入
差额常数。 将结果与捐赠金额减去交易费用进行比较。 如上所述,为方便测试和计算,我为
汽油设定了自己的价格。
使用
truffle test命令运行书面测试。 如果一切都正确完成,则结果应如下所示:

结果
我试图用一种简单易懂的语言来描述测试智能合约的步骤:从准备测试环境到自己编写测试。
现在,您可以测试任何智能合约并确保其正常运行。