第五章 以太坊智能合约的测试

其实本章另外还有web3的调用方法,但因为从没使用过nodejs做应用,我一直也没用过web3的方式调用。所以就先删掉了。

truffle的安装与使用

项目地址:http://truffleframework.com/

项目文档:http://truffleframework.com/docs/

truffle是一个去中心化应用的开发框架,也可以被用来测试、部署智能合约。项目说明里说自己为一个开发环境[目瞪狗呆脸]。但是我必须得说现在连UltraEdit都比它更像个IDE好吧?不过目前来看,还是最好用的一个DAPP(decentralized application去中心化应用)开发环境。

类似的项目有dapple(项目文档:http://dapple.readthedocs.io/en/master/)以及以太坊官方的meteor(项目文档:https://github.com/ethereum/wiki/wiki/Dapp-using-Meteor)。功能上都大同小异,如果有机会可以再试用一下,写相关的说明文档。

1 truffle安装与配置

1.1前置需求

nodejs5.0+

安装nodejs,需要去官网(nodejs.org)下载安装。在几年9月份,nodejs最新版本是6.8,推荐版本是4.5;到笔者发稿时,最新版本已经是7.0+,推荐版本是6.9了。所以考虑到比较高频率的版本更新,安装方法请直接参考官网。

1.2 truffle安装

使用npm安装

npm install -g truffle

2 truffle的使用

truffle是一个构建DAPP(decentralized application去中心化应用)的快捷工具。它主要由几个工具组成

2.1创建新项目

init命令的作用是在当前目录下建立一个truffle项目。在执行该命令之前,强烈建议先建立一个新目录。

$ mkdir myproject

$ cd myproject

$ truffle init

truffle项目的目录结构包括:

-app/:dapp目录,默认生成的dapp的建立目录;

-contracts/:合约目录,用于存放合约;

-migrations/:部署目录,用于存放部署合约用的脚本;

-test/:测试目录,用于存放测试应用程序和合约用的脚本;

-truffle.js-Truffle配置文件。

tips:在执行truffle init命令的时候,目录中会有一个范例项目MetaCoin。有兴趣的读者可以看一看这个项目的构成,本文在稍后的章节也会略微介绍一下这个项目。

2.2编译合约

在之前init建立的路径下执行编译命令。

$ truffle compile

编译所有位于contracts目录下的.sol文件,并生成相应的js文件,放入./build/contracts目录下。

需要注意的是,在编译合约时,合约文件中,必须存在与文件名完全一致的合约(包括大小写)。这句话的意思是,如果我的文件名叫MyContract.sol,那么这个文件中必须包含一个叫MyContract的合约,就像这样:

contract MyContract{

...

}

或者

library MyContract{

...

}

如果合约之间存在import依赖,那么也没关系,compile命令会自动按顺序来处理。

tips:第二次执行truffle compile命令时,只会编译那些与第一次编译相比有改动的合约。如果要强制全部编译,加上--compile-all选项。

tips:我经常用不到compile命令,而使用build命令代替。如果不构建基于javascript的去中心化app,只是测试智能合约的时候,使用二者是等同的。

2.3构建去中心化应用

在之前init建立的路径下执行构建命令。

$ truffle build

build命令的作用是构建去中心化应用。其作用是,首先执行编译合约,将生成合约的js文件放入./build/contracts/路径下(这一步与truffle compile功能一样)。之后将./app/目录下的文件拷贝到./build/目录下。

./app/目录结构如下:

app/

- javascripts/

- app.js

- stylesheets/

- app.css

- images/

- index.html

在配置文件truffle.js中,有一段代码是这样的:

build:{

"index.html": "index.html",

"app.js": [

"javascripts/app.js"

],

"app.css": [

"stylesheets/app.css"

],

"images/": "images/"

},

这段代码代表执行build命令时,会做出这样的操作:

(1)把./app/index.html拷贝到./build/index.html

(2)将app.js后面数组中的所有文件连接到一起(当前只有./javascripts/app.js一个文件),生成一个app.js文件。

tips:默认项目的./app/javascripts/app.js只有55行,而生成的app.js有44024行。前面的将近44000行代码,全部都是建立web3对象等等等等前置需求。最后把那55行代码复制到文件末尾……

(3)将app.css后面数组中的所有文件连接到一起(当前只有./stylesheets/app.js一个文件),生成一个app.css文件。

(4)将./app/images目录拷贝到./build/images

我们可以看到这里有个index.html。实际上,我们构建成功的应用,就是一个网站。如果要使用这个网站,需要之后执行serve命令。

2.4部署合约

2.4.1部署合约

在之前init建立的路径下执行部署命令。

$ truffle migrate

作用是执行migrations文件夹中的js脚本,通常功能是部署合约。其中migrations目录下的文件名称前缀必须是一个数字,部署时按顺序执行。在建立项目(init)时,会自动默认生成migration合约,其作用是将migrate的历史记录保存到区块链中。用来记录Migrate命令是否执行成功。

具体执行的脚本可以参考默认生成的项目脚本。比如,要部署一个叫做MyContract的合约,其Migration中就可以新建一个prefixNum_example_migration.js文件,内容如下:

module.exports = function(deployer) {

// deploymentsteps

deployer.deploy(MyContract);

};

Truffle要求我们在使用Migration机制的时候,必须使用Migration合约,这个Migration合约中会保存若干特殊的接口。正常情况下,这个Migration合约是第一个被部署的,而且以后再也不会被执行。

tips:如果第一次执行成功,第二次执行truffle migrate命令时,只会执行新的合约部署。如果要强制重新部署,要加上--reset选项。

./contracts/Migrations.sol合约内容如下:

contract Migrations {

addresspublic owner;

// Afunction with the signature `last_completed_migration()`, returning a uint, is required.

uintpublic last_completed_migration;

modifierrestricted() {

if(msg.sender == owner) _

}

functionMigrations() {

owner= msg.sender;

}

// Afunction with the signature `setCompleted(uint)` is required.

functionsetCompleted(uint completed) restricted {

last_completed_migration= completed;

}

functionupgrade(address new_address) restricted {

Migrationsupgraded = Migrations(new_address);

upgraded.setCompleted(last_completed_migration);

}

}

为了部署它,./migrations/1_initial_migration.js文件内容如下:

module.exports = function(deployer) {

deployer.deploy(Migrations);

};

2.4.2 deployer对象的使用

如果有两个合约A和B,在Migrations目录下的脚本中写入这样的代码

deployer.deploy(A);

deployer.deploy(B);

它们会按顺序先部署合约A,后部署合约B。

如果合约B的部署需要合约A的地址怎么办呢?Truffle也可以实现先部署合约A,再部署合约B,同时将A的地址传入。

deployer.deploy(A).then(function() {

returndeployer.deploy(B, A.address);

});

我们还可以在部署合约的时候,要求目标网络畅通:

module.exports = function(deployer, network){

// Adddemo data if we're not deploying to the live network.

if(network != "live") {

deployer.exec("add_demo_data.js");

}

}

到这里,我们可以看到deployer对象有很多的api接口,在这里列举一下。

(1)deployer.deploy(contract, args...)

在部署合约的时候,有些合约可能会需要一些参数,有些不需要,这些参数会作为合约构造函数的传入参数保存下来。那么我们部署合约的时候,就需要满足构造函数的要求。

示例代码:

//如果合约A的构造函数没有参数

deployer.deploy(A);

//如果合约A的构造函数有多个参数。

deployer.deploy(A, arg1, arg2, ...);

//如果同时部署多个合约。这样可以发起一个批量请求,而不是三个单独的请求。

deployer.deploy([

[A,arg1, arg2, ...],

B,

[C,arg1]

]);

(2)deployer.link(library, destinations)

将一个已经部署的合约库Library,链接到一个或多个合约。

示例代码:

// Deploy library LibA, then link LibA to contractB

deployer.deploy(LibA);

deployer.link(LibA, B);

// Link LibA to many contracts

deployer.link(LibA, [B, C, D]);

(3)deployer.autolink(contract)

自动将某个合约,链接到它依赖的已经部署的合约库Library

示例代码:

// Assume A depends on a LibB and LibC

deployer.deploy([LibB, LibC]);

deployer.autolink(A);

// Link *all* libraries to all available contracts

deployer.autolink();

(4)deployer.then(function() {...})

这个就像是一个条件,感受一下……

示例代码:

deployer.then(function() {

// Createa new version of A

returnA.new();

}).then(function(instance) {

// Setthe new instance of A's address on B.

varb = B.deployed();

returnb.setA(instance.address);

});

(5)deployer.exec(pathToFile)

示例代码:

deployer.exec("../path/to/file/demo_data.js");

部署的时候,执行程序。

2.5去中心化应用测试

在之前init建立的路径下执行测试命令。

$ truffle test

作用是执行test目录下的全部脚本文件(js/es/es6/jsx),通常功能是测试合约。

$ truffle test ./path/to/test/file.js

作用是执行指定路径下的测试脚本文件。

这些脚本应以contract()函数开头。(然而亲测不使用contract函数开头也不会报错……)contract()函数提供两个特性,①在运行脚本之前会重新部署合约,②可以输入账户地址。

这里给一个默认例子,作用是先部署合约,之后执行it块内的代码。

contract('MetaCoin', function(accounts) {

it("should put 10000 MetaCoin in thefirst account", function() {

// Get a reference to the deployed MetaCoincontract, as a JS object.

var meta = MetaCoin.deployed();

// Get the MetaCoin balance of the firstaccount and assert that it's 10000.

return meta.getBalance.call(accounts[0]).then(function(balance){

assert.equal(balance.valueOf(),10000, "10000 wasn't in the first account");

});

});

});

2.6与合约交互

(暂略)

2.7提供服务

$ truffle serve

作用是提供对外服务,启动truffle项目服务,部署到http 8080端口上。在build应用之后,项目的build目录下已经有了一个index.html、js脚本以及相应的网站设计css文件。如何把这个项目部署到Tomcat服务器上呢?只需要运行本命令就可以了!

2.8去中心应用MetaCoin

MetaCoin合约是Truffle默认建立的一个项目,简单的说,它是一个代币合约,类似于比特币,是基于智能合约建立的一种用于支付的数字货币。

2.8.1如何运行MetaCoin示例项目

简单的说,按照如下的步骤即可生成:

(1)建立目录

$ mkdir truffletest

$ cd truffletest

(2)建立项目

$ truffle init

(3)构建应用

$ truffle build

(4)部署合约

$ truffle migrate

(5)提供服务

$ truffle serve

打开浏览器,访问http://localhost:8080就可以运行这个去中心化应用了。


(默认项目MetaCoin的网站是这样子的,这里因为没有部署合约,显示的有点问题,正常情况下会显示有多少meta代币)

之后可以运行一些转账给另一个账户之类的功能。

2.9测试智能合约的简单说明

(1)建立项目

$ truffle init

(2)编写合约,将合约文件(*.sol)拷贝到./contracts目录下

(3)编译合约

$ truffle build

(4)修改配置

修改truffle.js,要部署的目标ip和端口,比如使用本地测试区块链testrpc,就修改为

rpc: {

host:"localhost",

port:8545

}

修改./migrations/目录下面的2_deploy_contracts.js文件,更改要部署的合约。

(5)部署合约,部署之前要确保测试区块链已经启动。如果使用testrpc,需要打开新终端,输入testrpc;如果使用pyethapp,需要运行pyethapp。诸如此类,等等等等。

$ truffle migrate

(6)编写测试脚本,拷贝到./test目录下

(7)测试合约

$ truffle test

class=MsoNorma�]���

你可能感兴趣的:(第五章 以太坊智能合约的测试)