如何测试智能合约

图片

智能合约的条款无法更改。 因此,无论何时创建智能合约,都需要确保其正常运行。 测试是在不同情况下测试合同的安全方法。 在本教程中,您将学习采取哪些步骤。

我会告诉:

  1. 如何准备测试环境。
  2. 如何编写JavaScript测试并在Truffle中执行它们。

本教程面向刚开始在以太坊上开发和测试智能合约的初学者。 要理解本教程,您必须至少具有JavaScriptSolidity的基础知识。

1.如何准备测试环境


有许多方法可以测试智能合约,但是Truffle是使用JavaScript的最受欢迎的测试编写工具。 在Truffle上,您可以编写单元测试也可以使用生产环境中的实际参数进行完全集成测试。

首先,您需要从官方站点安装最新版本的Node.js。
然后打开一个终端并使用以下命令安装Truffle

npm install -g truffle 

安装Truffle之后 ,无需关闭终端,请创建Funding目录:

 mkdir Funding 

接下来,使用以下命令转到目录:

 cd Funding 

要初始化目录,请运行以下命令:

 truffle init 

执行该命令后,将在Funding目录中创建以下文件夹和文件:

图片

此外,我们将处理每个目录。 同时,我们将继续准备测试环境。

要运行测试,您需要Javascript库。

Mocha是一个包含用于测试的常用功能(包括describeit)的库

Chai是一个支持多种检查功能的库。 检查结果有不同的“样式”,Chai为我们提供了这一机会。 在本教程中,我们将使用Should

默认情况下,Mocha是Truffle的一部分,我们可以轻松使用库函数。 柴必须手动安装。 为此,请使用终端,并在项目的根目录中运行命令:

 npm install chai 

我还安装了chai-bignumber库以比较任意精度的数字:

 npm install --save-dev chai-bignumber 

测试环境已准备就绪。 现在,您可以开始开发智能合约并对其进行测试。

2.如何编写JavaScript测试并在松露中执行它们


要开发测试,您需要一个智能合约。 我们将制定合同,使您可以收集捐款,设置特定金额以实现收集和提取资金。 如果有人捐出更多的钱,则已累计的金额和需要收取的金额之间的差额将退还给他。

转到资金->合同目录。 在“ 资金/合同”中,创建扩展名为.solFunding.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目录,并创建扩展名为.jstest_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是数组的第一个元素。 松露最多可使用十个地址,在测试中,我们只需要三个地址。

接受捐赠并结束募捐活动


在本节中,我们将检查:

  1. 捐款的接受程度和金额。
  2. 是否已达到一定数量的捐款。
  3. 如果您捐赠的钱超过了需要筹集的资金,将会发生什么。
  4. 捐款总额。
  5. 如果已收集到所需的金额,我可以继续筹集资金吗?

我们将编写并分析测试:

 . . . . . . . . . . . . . . . . . . . . . . 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); }); 

在解析测试之前,我要注意两点:

  1. 为了搜索事件(事件)并检查其参数,编写了一个小的findEvent函数。
  2. 为方便测试和计算,已设置了气体的固有值(恒定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命令运行书面测试。 如果一切都正确完成,则结果应如下所示:

图片

结果


我试图用一种简单易懂的语言来描述测试智能合约的步骤:从准备测试环境到自己编写测试。

现在,您可以测试任何智能合约并确保其正常运行。

Source: https://habr.com/ru/post/zh-CN432012/


All Articles