松露泡酒的功效与作用
In our introduction to Truffle, we discussed what Truffle is and how it can help you automate the job of compiling, testing and deploying smart contracts.
在我们对Truffle的介绍中 ,我们讨论了Truffle是什么以及它如何帮助您自动化编译,测试和部署智能合约的工作。
In this article, we’ll explore how to test smart contracts. Testing is the most important aspect of quality smart contract development.
在本文中,我们将探讨如何测试智能合约。 测试是质量智能合同开发的最重要的方面。
Why test extensively? So you can avoid stuff like this, or this. Smart contracts handle value, sometimes a huge amount of value — which makes them very interesting prey for people with the time and the skills to attack them.
为什么要进行广泛测试? 因此,您可以避免这种或这种事情 。 智能合约可以处理价值,有时价值巨大,这使它们成为具有时间和技能的人的非常有趣的猎物。
You wouldn’t want your project to end up on the Blockchain graveyard, would you?
您不希望您的项目最终出现在Blockchain墓地上 ,对吗?
入门 (Getting Started)
We’re going to be making HashMarket, a simple, smart-contract based used goods market.
我们将使HashMarket成为一个简单,基于智能合约的二手商品市场。
Open your terminal and position yourself in the folder where you want to build the project. In that folder, run the following:
打开终端,将您自己放置在要生成项目的文件夹中。 在该文件夹中,运行以下命令:
mkdir HashMarket
cd HashMarket
truffle init
You should get a result that looks a little bit like this:
您应该得到的结果看起来像这样:
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
You’ll also get a file structure that looks like this:
您还将获得一个如下所示的文件结构:
.
├── contracts
│ └── Migrations.sol
├── migrations
│ └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js
For a refresher on the files, take a look at the previous article. In a nutshell, we have the basic truffle.js
and the two files used for making the initial migrations onto the blockchain.
要回顾这些文件,请阅读上一篇文章 。 简而言之,我们拥有基本的truffle.js
和用于初始迁移到truffle.js
的两个文件。
准备测试环境 (Preparing the test environment)
The easiest way to test is on the local network. I highly recommend using the ganache-cli
(previously known as TestRPC
) tool for contract testing.
最简单的测试方法是在本地网络上。 我强烈建议使用ganache-cli
(以前称为TestRPC
)工具进行合同测试。
Install ganache-cli
(which requires the Node Package Manager):
安装ganache-cli
(需要Node Package Manager):
npm install -g ganache-cli
After that, open a separate terminal window or tab and run this:
之后,打开一个单独的终端窗口或选项卡并运行以下命令:
ganache-cli
You should see an output similar to this:
您应该看到类似于以下的输出:
Ganache CLI v6.1.0 (ganache-core: 2.1.0)
Available Accounts
==================
(0) 0xd14c83349da45a12b217988135fdbcbb026ac160
(1) 0xc1df9b406d5d26f86364ef7d449cc5a6a5f2e8b8
(2) 0x945c42c7445af7b3337834bdb1abfa31e291bc40
(3) 0x56156ea86cd46ec57df55d6e386d46d1bbc47e3e
(4) 0x0a5ded586d122958153a3b3b1d906ee9ff8b2783
(5) 0x39f43d6daf389643efdd2d4ff115e5255225022f
(6) 0xd793b706471e257cc62fe9c862e7a127839bbd2f
(7) 0xaa87d81fb5a087364fe3ebd33712a5522f6e5ac6
(8) 0x177d57b2ab5d3329fad4f538221c16cb3b8bf7a7
(9) 0x6a146794eaea4299551657c0045bbbe7f0a6db0c
Private Keys
==================
(0) 66a6a84ee080961beebd38816b723c0f790eff78f0a1f81b73f3a4c54c98467b
(1) fa134d4d14fdbac69bbf76d2cb27c0df1236d0677ec416dfbad1cc3cc058145e
(2) 047fef2c5c95d5cf29c4883b924c24419b12df01f3c6a0097f1180fa020e6bd2
(3) 6ca68e37ada9b1b88811015bcc884a992be8f6bc481f0f9c6c583ef0d4d8f1c9
(4) 84bb2d44d64478d1a8b9d339ad1e1b29b8dde757e01f8ee21b1dcbce50e2b746
(5) 517e8be95253157707f34d08c066766c5602e519e93bace177b6377c68cba34e
(6) d2f393f1fc833743eb93f108fcb6feecc384f16691250974f8d9186c68a994ef
(7) 8b8be7bec3aca543fb45edc42e7b5915aaddb4138310b0d19c56d836630e5321
(8) e73a1d7d659b185e56e5346b432f58c30d21ab68fe550e7544bfb88765235ae3
(9) 8bb5fb642c58b7301744ef908fae85e2d048eea0c7e0e5378594fc7d0030f100
HD Wallet
==================
Mnemonic: ecology sweet animal swear exclude quote leopard erupt guard core nice series
Base HD Path: m/44'/60'/0'/0/{account_index}
Listening on localhost:8545
This is a listing of all the accounts ganache-cli
created for you. You can use any account you wish, but these accounts will be preloaded with ether, so that makes them very useful (since testing requires ether for gas costs).
这是ganache-cli
为您创建的所有帐户的列表。 您可以使用任何您想要的帐户,但是这些帐户将预装以太币,因此使它们非常有用(因为测试需要以太币支付燃气费用 )。
After this, go to your truffle.js
or truffle-config.js
file and add a development network to your config:
之后,转到您的truffle.js
或truffle.js
truffle-config.js
文件,并将开发网络添加到您的配置中:
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*"
}
}
};
编写智能合约 (Writing the smart contract)
The first thing we’ll do is write the HashMarket smart contract. We’ll try to keep it as simple as possible, while still retaining the required functionality.
我们要做的第一件事是编写HashMarket智能合约。 我们将尝试使其尽可能简单,同时仍保留所需的功能。
HashMarket is a type of eBay on the blockchain. It enables sellers to post products and buyers to buy them for ether. It also enables sellers to remove products if they’re not sold.
HashMarket是区块链上的一种eBay。 它使卖方能够发布产品,而买方则可以购买产品以太。 如果不出售产品,卖家也可以将其删除。
In your project, in the contracts
folder, create a new file and call it HashMarket.sol
. In that file, add the following code:
在项目的contracts
文件夹中,创建一个新文件,并将其HashMarket.sol
。 在该文件中,添加以下代码:
pragma solidity 0.4.21;
contract HashMarket {
// Track the state of the items, while preserving history
enum ItemStatus {
active,
sold,
removed
}
struct Item {
bytes32 name;
uint price;
address seller;
ItemStatus status;
}
event ItemAdded(bytes32 name, uint price, address seller);
event ItemPurchased(uint itemID, address buyer, address seller);
event ItemRemoved(uint itemID);
event FundsPulled(address owner, uint amount);
Item[] private _items;
mapping (address => uint) public _pendingWithdrawals;
modifier onlyIfItemExists(uint itemID) {
require(_items[itemID].seller != address(0));
_;
}
function addNewItem(bytes32 name, uint price) public returns (uint) {
_items.push(Item({
name: name,
price: price,
seller: msg.sender,
status: ItemStatus.active
}));
emit ItemAdded(name, price, msg.sender);
// Item is pushed to the end, so the lenth is used for
// the ID of the item
return _items.length - 1;
}
function getItem(uint itemID) public view onlyIfItemExists(itemID)
returns (bytes32, uint, address, uint) {
Item storage item = _items[itemID];
return (item.name, item.price, item.seller, uint(item.status));
}
function buyItem(uint itemID) public payable onlyIfItemExists(itemID) {
Item storage currentItem = _items[itemID];
require(currentItem.status == ItemStatus.active);
require(currentItem.price == msg.value);
_pendingWithdrawals[currentItem.seller] = msg.value;
currentItem.status = ItemStatus.sold;
emit ItemPurchased(itemID, msg.sender, currentItem.seller);
}
function removeItem(uint itemID) public onlyIfItemExists(itemID) {
Item storage currentItem = _items[itemID];
require(currentItem.seller == msg.sender);
require(currentItem.status == ItemStatus.active);
currentItem.status = ItemStatus.removed;
emit ItemRemoved(itemID);
}
function pullFunds() public returns (bool) {
require(_pendingWithdrawals[msg.sender] > 0);
uint outstandingFundsAmount = _pendingWithdrawals[msg.sender];
if (msg.sender.send(outstandingFundsAmount)) {
emit FundsPulled(msg.sender, outstandingFundsAmount);
return true;
} else {
return false;
}
}
}
After you’ve done this, try running truffle compile
to see if the code will compile. Since Solidity tends to change conventions, if your code won’t compile, the likely solution is using an older version of the compiler (0.4.21. is the version this was written with and will be fine).
完成此操作后,请尝试运行truffle compile
以查看代码是否可以编译。 由于Solidity倾向于更改约定,因此,如果您的代码无法编译,则可能的解决方案是使用较旧版本的编译器(0.4.21是该版本的编译器,并且会很好)。
编写迁移 (Writing the migration)
You need to write a migration to let Truffle know how to deploy your contract to the blockchain. Go into the migrations
folder and create a new file called 2_deploy_contracts.js
. In that file, add the following code:
您需要编写迁移文件,以使Truffle知道如何将合同部署到区块链。 进入migrations
文件夹并创建一个名为2_deploy_contracts.js
的新文件。 在该文件中,添加以下代码:
var HashMarket = artifacts.require("./HashMarket.sol");
module.exports = function(deployer) {
deployer.deploy(HashMarket);
};
Since we only have one contract, the migrations file is very simple.
由于我们只有一份合同,因此迁移文件非常简单。
Now run truffle migrate
and hopefully you’ll get something like this:
现在运行truffle migrate
,希望您会得到如下信息:
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0xad501b7c4e183459c4ee3fee58ea9309a01aa345f053d053b7a9d168e6efaeff
Migrations: 0x9d69f4390c8bb260eadb7992d5a3efc8d03c157e
Saving successful migration to network...
... 0x7deb2c3d9dacd6d7c3dc45dc5b1c6a534a2104bfd17a1e5a93ce9aade147b86e
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying HashMarket...
... 0xcbc967b5292f03af2130fc0f5aaced7080c4851867abd917d6f0d52f1072d91e
HashMarket: 0x7918eaef5e6a21a26dc95fc95ce9550e98e789d4
Saving successful migration to network...
... 0x5b6a332306f739b27ccbdfd10d11c60200b70a55ec775e7165358b711082cf55
Saving artifacts...
测试智能合约 (Testing Smart Contracts)
You can use Solidity or JavaScript for smart contract testing. Solidity can be a little bit more intuitive when testing smart contracts, but JavaScript gives you many more possibilities.
您可以使用Solidity或JavaScript进行智能合约测试。 在测试智能合约时,可靠性可以更加直观,但是JavaScript为您提供了更多的可能性。
坚固性测试 (Solidity testing)
In order to start testing, in the test
folder in your project, create a file called TestHashMarket.sol
. The Truffle suite provides us with helper functions for testing, so we need to import those. At the beginning of the file, add:
为了开始测试,请在项目的test
文件夹中,创建一个名为TestHashMarket.sol
的文件。 松露套件为我们提供了用于测试的助手功能,因此我们需要导入这些功能。 在文件的开头,添加:
pragma solidity ^0.4.20;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/HashMarket.sol";
The first two imports are the important ones.
前两个进口是重要的进口。
The Assert
import gives us access to various testing functions, like Assert.equals()
, Assert.greaterThan()
, etc. In this manner, Assert works with Truffle to automate most “boring” code writing.
Assert
导入使我们可以访问各种测试功能,例如Assert.equals()
, Assert.greaterThan()
等。通过这种方式,Assert与Truffle配合使用可自动执行大多数“无聊的”代码编写。
The DeployedAddresses
import manages contract addresses for us. Since every time you change your contract, you must redeploy it to a new address and even if you don’t change it, every time you test the contract should be redeployed to start from pristine state. The DeployedAddresses
library manages this for us.
DeployedAddresses
导入为我们管理合同地址。 由于每次更改合同时,都必须将其重新部署到新地址,即使您没有更改它,每次测试合同时都应重新部署以从原始状态开始。 DeployedAddresses
库为我们进行管理。
Now let’s write a test. Under the import
directives, add the following:
现在让我们编写一个测试。 在import
指令下,添加以下内容:
contract TestHashMarket {
function testAddingNewProduct() public {
// DeployedAddresses.HashMarket() handles contract address
// management for us
HashMarket market = HashMarket(DeployedAddresses.HashMarket());
bytes32 expectedName = "T";
uint expectedPrice = 1000;
uint itemID = market.addNewItem(expectedName, expectedPrice);
bytes32 name;
uint price;
address seller;
uint status;
(name, price, seller, status) = market.getItem(itemID);
Assert.equal(name, expectedName, "Item name should match");
Assert.equal(price, expectedPrice, "Item price should match");
Assert.equal(status, uint(HashMarket.ItemStatus.active), "Item status at creation should be .active");
Assert.equal(seller, this, "The function caller should be the seller");
}
}
Let’s look at some of the important parts of a test. Firstly:
让我们看一下测试的一些重要部分。 首先:
HashMarket market = HashMarket(DeployedAddresses.HashMarket());
This code uses the DeployedAddresses
library to create a new instance of the HashMarket
contract for testing.
此代码使用DeployedAddresses
库创建HashMarket
合同的新实例以进行测试。
Assert.equal(<current>, <expected>, <message>)
This part of the code takes care of checking whether two values are equal. If yes, it communicates a success message to the test suite. If not, it communicates a failure. It also appends the message so you can know where your code failed.
代码的这一部分负责检查两个值是否相等。 如果是,它将成功消息传达给测试套件。 如果不是,则表示失败。 它也将追加信息,这样就可以知道你的代码失败。
Now let’s run this:
现在运行此命令:
truffle test
You should get a result like this:
您应该得到如下结果:
TestHashMarket
1) testAddingNewProduct
> No events were emitted
0 passing (879ms)
1 failing
1) TestHashMarket testAddingNewProduct:
Error: VM Exception while processing transaction: revert
at Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:38:1)
at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:86:1
at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1
at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1)
at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1)
at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:469:1)
at endReadableNT (_stream_readable.js:1106:12)
at process._tickCallback (internal/process/next_tick.js:178:19)
Our test failed :-(
我们的测试失败了:-(
Let’s go into the contract to inspect possible errors.
让我们进入合同以检查可能的错误。
After a careful inspection, we’ll find that the issue with our contract is in the return
statement of our addNewItem
method:
经过仔细检查,我们发现合同的问题在addNewItem
方法的return
语句中:
function addNewItem(bytes32 name, uint price) public returns (uint) {
_items.push(Item({
name: name,
price: price,
seller: msg.sender,
status: ItemStatus.active
}));
// Item is pushed to the end, so the lenth is used for
// the ID of the item
return _items.length;
}
Since arrays are zero indexed, and we use the array position as an ID, we should actually return _items.length - 1
. Fix this error and run this again:
由于数组的索引为零,并且我们将数组位置用作ID,因此实际上应该返回_items.length - 1
。 解决此错误,然后再次运行:
truffle test
You should get a much happier message:
您应该得到一条快乐得多的消息:
TestHashMarket
✓ testAddingNewProduct (130ms)
1 passing (729ms)
We’ve successfully used Truffle testing to fix a very likely error in our code!
我们已成功使用Truffle测试来修复代码中很可能出现的错误!
JavaScript测试 (JavaScript Testing)
Truffle enables us to use JavaScript for testing, leveraging the Mocha testing framework. This enables you to write more complex tests and get more functionality out of your testing framework.
Truffle使我们能够利用Mocha测试框架来使用JavaScript进行测试。 这使您可以编写更复杂的测试,并从测试框架中获得更多功能。
Okay, let’s write the test. First, in the test
folder, create a file and call it hashmarket.js
.
好吧,让我们编写测试。 首先,在test
文件夹中,创建一个文件并将其命名为hashmarket.js
。
The first thing we need to do, is get the reference to our contract in JavaScript. For that, we’ll use Truffle’s artifacts.require(...)
function:
我们需要做的第一件事是获取JavaScript中的合同参考。 为此,我们将使用松露的artifacts.require(...)
函数:
var HashMarket = artifacts.require("./HashMarket.sol");
Now that we have the reference to the contract, let’s start writing tests. To start, we’ll use the contract
function provided to us:
现在我们已经有了合同的参考,让我们开始编写测试。 首先,我们将使用提供给我们的contract
功能:
contract("HashMarket", function(accounts) {
});
This creates a test suite for our contract. Now for testing, we use Mocha it
syntax:
这将为我们的合同创建一个测试套件。 现在进行测试,我们使用Mocha it
语法:
contract("HashMarket", function(accounts) {
it("should add a new product", function() {
});
});
This describes the test we’ll write and presents a message for us to know the purpose of the test. Now let’s write the test itself. At the end, the hashmarket.js
file should look like the following. The reasoning is explained in the comments of the source code:
这描述了我们将要编写的测试,并提供了一条消息让我们知道测试的目的。 现在让我们自己编写测试。 最后, hashmarket.js
文件应如下所示。 解释在源代码的注释中说明:
var HashMarket = artifacts.require("./HashMarket.sol");
contract("HashMarket", function(accounts) {
it("should add a new product", function() {
// Set the names of test data
var itemName = "TestItem";
var itemPrice = 1000;
var itemSeller = accounts[0];
// Since all of our testing functions are async, we store the
// contract instance at a higher level to enable access from
// all functions
var hashMarketContract;
// Item ID will be provided asynchronously so we extract it
var itemID;
return HashMarket.deployed().then(function(instance) {
// set contract instance into a variable
hashMarketContract = instance;
// Subscribe to a Solidity event
instance.ItemAdded({}).watch((error, result) => {
if (error) {
console.log(error);
}
// Once the event is triggered, store the result in the
// external variable
itemID = result.args.itemID;
});
// Call the addNewItem function and return the promise
return instance.addNewItem(itemName, itemPrice, {from: itemSeller});
}).then(function() {
// This function is triggered after the addNewItem call transaction
// has been mined. Now call the getItem function with the itemID
// we received from the event
return hashMarketContract.getItem.call(itemID);
}).then(function(result) {
// The result of getItem is a tuple, we can deconstruct it
// to variables like this
var [name, price, seller, status] = result;
// Start testing. Use web3.toAscii() to convert the result of
// the smart contract from Solidity bytecode to ASCII. After that
// use the .replace() to pad the excess bytes from bytes32
assert.equal(itemName, web3.toAscii(name).replace(/\u0000/g, ''), "Name wasn't properly added");
// Use assert.equal() to check all the variables
assert.equal(itemPrice, price, "Price wasn't properly added");
assert.equal(itemSeller, seller, "Seller wasn't properly added");
assert.equal(status, 0, "Status wasn't properly added");
});
});
});
Run truffle test
and you should get something like this:
运行truffle test
,您应该会得到以下信息:
TestHashMarket
✓ testAddingNewProduct (109ms)
Contract: HashMarket
✓ should add a new product (64ms)
2 passing (876ms)
Your test has passed and you can be confident about not having regression bugs.
您的测试已通过,您可以确定没有回归错误。
For homework, write tests for all other functions in the contract.
对于家庭作业,请编写合同中所有其他功能的测试。
翻译自: https://www.sitepoint.com/truffle-testing-smart-contracts/
松露泡酒的功效与作用