测试智能合约

Truffle 使用Mocha测试框架和Chai进行测试,提供了一个可编写JavaScript测试的可靠框架。

以js测试方法为例。在test目录下建立 market.js 测试文件。写出框架代码

var Market = artifacts.require('./Market.sol');

contract('Market', function(accounts){

    it('add Commodity test....', function(){
        return Market.deployed().then(function(instance){
            marketInstance = instance;
        })
    });
});

如果你熟悉js 的promise特性的话,这些代码的信息量不算大。

artifacts.require()

在migration开始时,我们通过artifacts.require()方法告诉Truffle我们想要与之交互的合约。 这个方法类似于Node的require,但在我们的例子中,它特别返回了一个可以在我们部署脚本的其余部分中都能使用的合约抽象。 指定的名称应与该源文件中的合约定义的名称相匹配。 不要传递源文件的名称,因为一个sol文件可以包含多个合约。

contract()

让Truffle测试与Mocha不同的是contract()函数:该函数的工作方式与describe()完全相同,只是它可以启用Truffle的clean-room功能。

clean-room:

在针对Ganache或Truffle Develop运行测试时,Truffle将使用高级快照功能来确保测试文件不会彼此共享状态。 当与其他以太坊客户一起运行时,Truffle将在每个测试文件开始时重新部署,以确保拥有一组新的测试合约。

特点:

  • 在运行每个contract()函数之前,合约将重新部署到正在运行的以太坊客户端,以便让其中的测试以clean-room状态运行。
  • contract()函数提供了本地Ethereum测试客户端可用的测试帐户列表,一般为10个包括地址和私钥。

下面来实际测试下添加物品的方法:

var Market = artifacts.require('./Market.sol');

contract('Market', function(accounts){

    it('add a Commodity test....', function(){
        return Market.deployed().then(function(instance){
            marketInstance = instance;

            return marketInstance.addCommodity("物品1", "qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn", 100, {from: accounts[0]});
        })
        .then(res =>{
            console.log(res);
        })
    });
});

addCommodity 怎么多出了一个参数?

不得不说一下 accounts 这个变量,它是测试框架提供的一系列测试账号,因此是个数组。在每个我们自己写的方法最后都要传一个地址参数,来表明在实际情况中是谁调用了该合约。在这里使用第一个测试账号。

希望你已经在上一节配置好了基本环境。别忘了清空test目录下除market.js的所有文件,不然执行测试的时候会自动执行其他不必要的测试方法。

下面在下面根目录执行

truffle test

全是绿色的,表明测试通过。但这不是正式的测试,因为还没有具体值去assert,不能掉以轻心。

这里看到了一堆json字符串,它们是以太坊的交易数据,一笔交易的哈希叫tx,还包括收据信息等等一系列的区块信息。

下面测试updateCommodity 方法,为了检验update的效果我们还需要在合约定义查看物品的方法:

function getCommodity() commodityExist(msg.sender) returns (Commodity){
    return warehouse[msg.sender];
}

逻辑比较简单不多加赘述了。

下面修改测试文件,如果测试执行的方法很多,这样是不利于单独测试的,因此分开写最好::

var Market = artifacts.require('./Market.sol');


contract('Market', function(accounts){

    it('add Commodity test....', function(){
        return Market.deployed().then(function(instance){
            marketInstance = instance;

            return marketInstance.addCommodity("物品1", "qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn", 100, {from: accounts[0]});
        })
        .then(res => {
            return marketInstance.getCommodity({from: accounts[0]});
        })
        .then(commodity => {
            assert.equal(commodity.name,  "物品1", '物品名字 叫 物品1');
            assert.equal(commodity.url,  "qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn", 'url 是qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn');
            assert.equal(commodity.price,  100, '物品价格是100');
        })
    });

    it('update Commodity test....', function(){
        return Market.deployed().then(function(instance){
            marketInstance = instance;

            return marketInstance.updateCommodity("显卡", 4000, {from: accounts[0]});
        })
        .then(res => {
            return marketInstance.getCommodity({from: accounts[0]});
        })
        .then(commodity => {
            assert.equal(commodity.name,  "显卡", '物品名字 叫 显卡');
            assert.equal(commodity.price,  4000, '物品价格是4000');        })
    });
});

改动之后我们出价2 ether转让显卡。

之后还是直接部署命令就可以了,然后进行test:

truffle migration
truffle test

结果是undefined 。可是明明定义好了返回的Commodity对象的。通过查阅更多资料,我们发现:

问题出在getCommodity方法,直接返回一个struct是不行的!

必须如下改动,把参数一个个的传出来:

    function getCommodity() commodityExist(msg.sender) returns (string name, string url, uint price){
        name =  warehouse[msg.sender].name;
        url = warehouse[msg.sender].url;
        price = warehouse[msg.sender].price;
    }

然后再次修改测试代码,重新migration、test

var Market = artifacts.require('./Market.sol');

contract('Market', function(accounts){

    it('add Commodity test....', function(){
        return Market.deployed().then(function(instance){
            marketInstance = instance;

            return marketInstance.addCommodity("物品1", "qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn", 100, {from: accounts[0]});
        })
        .then(res => {
            return marketInstance.getCommodity.call({from: accounts[0]});
        })
        .then((res) => {
            assert.equal(res[0],  "物品1", '物品名字 叫 物品1');
            assert.equal(res[1],  "qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn", 'url 是qms2hjwx8qejwm4nmwu7ze6ndam2sfums3x6idwz5myzbn');
            assert.equal(res[2],  web3.toWei(100), '物品价格是100');
            console.log(res);
        });
    });
});

结果输出:

以上代码需要注意:

  • 调用getCommodity方法后面要加 ".call",这种无参方法要借助call才行,同时传入地址。
  • 根据log返回参数是一个数组,里面顺序包含了在sol文件中returns的参数,尤其是res[2]这个参数的值是wei类型,因此需要借助web3.toWei()来把数值转换过来。如前文提到的,转账单位必须是ether、wei等等货币单位。

你可以自己测试updateCommodity方法。试着修改{from: accounts[0]},如果updateCommodity和addCommodity传入地址不一样会发生什么?

results matching ""

    No results matching ""