29 编写合约测试脚本

一、
在编写合约时,我们可以利用remix部署后的页面调用合约函数,进行单元测试;还可以将合约部署到私链,用geth控制台或者node命令行进行交互测试。但这有很大的随意性,并不能形成标准化测试流程;而且手动一步步操作,比较繁琐,不易保证重复一致。
于是我们想到,是否可以利用现成的前端技术栈实现合约的自动化测试呢?当然是可以的,mocha就是这样一个JavaScript测试框架。
1、安装依赖
开始编写测试脚本之前,我们首先需要安装依赖:测试框架mocha。当然,作为对合约的测试,模拟节点ganache和web3(1.0.0)都是不可缺少的.

npm install mocha@5.2.0 --save--dev

进行单元测试,比较重要的一点是保证测试的独立性和隔离性,所以我们并不需要测试网络这种有复杂交互的环境,甚至不需要本地私链保存测试历史。而ganache基于内存模拟以太坊节点行为,每次启动都是一个干净的空白环境,所以非常适合我们做开发时的单元测试。还记得ganache的前身是testRPC。

2、mocha简介
mocha是JavaScript的一个单元测试框架,既可以在浏览器环境中运行,也可以在node.js环境下运行。我们只需要编写测试用例,mocha会将测试自动运行并给出测试结果。
mocha的主要特点有:

  • 既可以测试简单的JavaScript函数,又可以测试异步代码;
  • 可以自动运行所有测试,也可以只运行特定的测试;
  • 可以支持before、after、beforeEach和afterEach来编写初始化代码。

3、测试脚本示例
假设我们编写了一个sum.js,并且输出一个简单的求和函数:

const assert = require('assert');
const sum = require('./sum.js');

assert.strictEqual( sum(), 0);
assert.strictEqual( sum(1), 1);
assert.strictEqual( sum(1,2), 3);
assert.strictEqual( sum(1,2,3), 6);

console.log('all assert passed');

在这里插入图片描述
assert模块非常简单,它断言一个表达式为true。如果断言失败,就抛出Error。
单独写一个test.js的缺点是没法自动运行测试,而且,如果第一个assert报错,后面的测试也执行不了了。
如果有很多测试需要运行,就必须把这些测试全部组织起来,然后统一执行,并且得到执行结果。这就是我们为什么要用mocha来编写并运行测试。
4、我们利用mocha修改后的测试脚本如下:

const assert = require('assert');
const sum = require('./sum.js');

describe('#sum.js', ()=>{
describe('#sum()', ()=>{
        it('sum() should return 0', ()=>{
                assert.strictEqual( sum(), 0);
        });
        it('sum(1) should return 1', ()=>{
                assert.strictEqual( sum(1), 1);
        });
        it('sum(1,2) should return 3', ()=>{
                assert.strictEqual( sum(1,2), 3);
        });
        it('sum(1,2,3) should return 6', ()=>{
                assert.strictEqual( sum(1,2,3), 6);
        });
});
});

在这里插入图片描述

二、
测试时我们通常会把每次测试运行的环境隔离开,以保证互不影响。对应到合约测试,我们每次测试都需要部署新的合约实例,然后针对新的实例做功能测试。 Car 合约的功能比较简单,我们只要设计2个测试用例:

  • 合约部署时传入的 brand 属性被正确存储;
  • 调用 setBrand 之后合约的 brand 属性被正确更新;
    新建测试文件tests/car.spec.js,完整的测试代码如下。
const assert = require('assert');
const path = require('path');
const ganache = require('ganache-cli');
const Web3 = require('web3');

const web3 = new Web3(ganache.provider());

const contractPath = path.resolve(__dirname, './compiled/Car.json');
const {interface, bytecode} = require(contractPath);

let contract;
let accounts;
const initialBrand = 'BMW';

describe('#contract', ()=>{
        beforeEach(async ()=>{
        accounts = await web3.eth.getAccounts();
        contract = await new web3.eth.Contract(JSON.parse(interface))
                                .deploy({data:bytecode, arguments:[initialBrand]})
                                .send({from:accounts[0], gas:3000000});
});
it('deployed contract sucessfully', ()=>{
assert.ok(contract.options.address);
});
});

在这里插入图片描述
整个测试代码使用的断言库是 Node.js 内置的 assert 模块,assert.ok() 用于判断表达式真值,等同于assert(),如果为false则抛出error;assert.equal() 用于判断实际值和期望值是否相等(==),如果不相等则抛出error。
beforeEach是mocha里提供的声明周期方法,表示每次运行时每个test执行前都要做的准备操作。因为我们知道,在测试前初始化资源,测试后释放资源是非常常见的,所以mocha提供了before、after、beforeEach和afterEach来实现这些功能。
测试的关键步骤也用编号的数字做了注释,其中步骤 1、2、3 在合约部署脚本中已经比较熟悉,需要注意的是 ganache-cli provider 的创建方式。我们在脚本中引入ganache,将模拟以太坊节点嵌入测试中,就不会影响我们外部运行的节点环境了。
测试中我们用到了web3.js中两个与合约实例交互的方法,之前我们已经接触过,以后在 DApp 开发时会大量使用:

  • contract.methods.brand().call(),调用合约上的方法,通常是取数据,立即返回,与v0.20.1版本中的 .call() 相同;
  • contract.methods.setBrand(‘xxx’).send(),对合约发起交易,通常是修改数据,返回的是交易 Hash,相当于v0.20.1中的sendTransaction() ;
    send 必须指定发起的账户地址,而 call 可以直接调用。
    注意在v1.0.0中,contract后面要加上.methods然后才能跟合约函数名,这与v0.20.1不同;类似,v1.0.0中事件的监听也要contract后面加.events。
const assert = require('assert');
const path = require('path');
const ganache = require('ganache-cli');
const Web3 = require('web3');

const web3 = new Web3(ganache.provider());

const contractPath = path.resolve(__dirname, './compiled/Car.json');
const {interface, bytecode} = require(contractPath);

let contract;
let accounts;
const initialBrand = 'BMW';

describe('#contract', ()=>{
        beforeEach(async ()=>{
        accounts = await web3.eth.getAccounts();
        contract = await new web3.eth.Contract(JSON.parse(interface))
                                .deploy({data:bytecode, arguments:[initialBrand]})
                                .send({from:accounts[0], gas:3000000});
});
it('deployed contract sucessfully', ()=>{
        assert.ok(contract.options.address);
});
it('should has a initial brand', async ()=>{
        let brand = await contract.methods.brand().call();
        assert.equal(brand, initialBrand);
});
it('should set a new brand', async ()=>{
        const newBrand = 'Audi';
        await contract.methods.setBrand(newBrand)
                .send({from:accounts[0]});
        let brand = await contract.methods.brand().call();
        assert.equal(brand, newBrand);
});
});

在这里插入图片描述

const assert = require('assert');
const path = require('path');
const ganache = require('ganache-cli');
const Web3 = require('web3');

const web3 = new Web3(ganache.provider());

const contractPath = path.resolve(__dirname, './compiled/Car.json');
const {interface, bytecode} = require(contractPath);

let contract;
let accounts;
const initialBrand = 'BMW';

describe('#contract', ()=>{
        before(()=>{
                console.log('测试开始!');
        });
        beforeEach(async ()=>{
        accounts = await web3.eth.getAccounts();
        contract = await new web3.eth.Contract(JSON.parse(interface))
                                .deploy({data:bytecode, arguments:[initialBrand]})
 .send({from:accounts[0], gas:3000000});

        console.log('合约已部署!');
        });

        after(()=>{
                console.log('测试结束!');
        });
        afterEach(()=>{
                console.log('当前测试已完成!');
        });
it('deployed contract sucessfully', ()=>{
        assert.ok(contract.options.address);
});
it('should has a initial brand', async ()=>{
        let brand = await contract.methods.brand().call();
        assert.equal(brand, initialBrand);
});
it('should set a new brand', async ()=>{
        const newBrand = 'Audi';
        await contract.methods.setBrand(newBrand)
                .send({from:accounts[0]});
        let brand = await contract.methods.brand().call();
 assert.equal(brand, newBrand);
});
});

在这里插入图片描述
三、完整工作流
到目前为止,我们已经熟悉了智能合约的开发、编译、部署、测试,而在实际工作中,把这些过程串起来才能算作是真正意义上的工作流。比如修改了合约代码需要重新运行测试,但是重新运行测试之前需要重新编译,而部署的过程也是类似的,每次部署的都要是最新的合约代码。
通过 npm script 机制,我们可以把智能合约的工作流串起来,让能自动化的尽可能自动化,在 package.json 中作如下修改:

"scripts": { 
    "compile": "./scripts/compile.js", 
	"pretest": "npm run compile",
	"test": "./node_modules/mocha/bin/mocha tests/",
	"predeploy": "npm run compile",
	"deploy": "node ./scripts/deploy.js"
},

在这里插入图片描述
四、总结
测试脚本的编译

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值