https://mochajs.org/ 学习网址: https://www.jianshu.com/p/9c78548caffa https://www.jb51.net/article/106463.htm 在truffle框架的简单使用中,咱们了解到它的测试模块是包装了mocha测试框架的,在这里咱们选择cryptopunks的truffle例子来相应讲解:javascript
https://github.com/larvalabs/cryptopunkshtml
为何要使用mocha这个测试模块:前端
当咱们在开发,咱们每每会有如下的问题:java
参考:https://blog.csdn.net/imwebteam/article/details/53310958node
当一份需求来了, 开发人员每每不能百分百的理解需求的内容(抛弃产品本身变动需求的可能性。。),这每每会让开发人员开发出的功能会有跟需求有所差异,这会带来额外的工做量git
什么是开发和测试脱节,说的是,当开发人员按照本身的想法开发完了一个需求。而后测试人员也按照本身的想法去测试这个需求,而后因为双方的分歧,致使测试认为开发有bug,开发认为测试是sb.github
那么如何解决上面的问题呢?web
答案就是 选择一种软件敏捷开发模式npm
目前比较流行的开发模式有两种: TDD 和 BDDjson
两种方式各有其特色,咱们一般选择的是BDD的方式
为了方便咱们编写测试用例,咱们须要使用一些前端测试用例工具——mocha
在这个例子中咱们可以看见在其的test文件夹中写了一些测试文件,好比咱们以cryptopunksmarket-setinitia.jsl这个文件为例,看cryptopunks测试代码cryptopunksmarket-setinitial.js
(1)Truffle测试框架学习:
参考:http://www.blockchainbrother.com/article/2082
Truffle 使用 Mocha 测试框架 和 Chai 断言来给你提供一个可靠的框架编写JavaScript测试。这里须要注意到的一个很大的不一样是它使用了contract()测试套件替代了describe()测试套件,这个函数基本和 describe() 同样,只不过它能够启用clean-room 功能. 其过程以下:
(2)后面想要使用测试框架mocha,可是又不想使用truffle,因此就来学习这个框架怎么单独使用了,下面就是学习的过程:
固然,首先要安装:
使用npm全局安装:
npm install --g mocha
或者仅仅只是安装在某个模块:
npm install --save mocha
而后你就可使用了
1.要测试上面的代码是否对的,所以就要编写测试脚本,测试脚本与所要测试的源码脚本同名,可是后缀名为 .test.js或 .spec.js, 如:xx.test.js 或 xx.spec.js
2.测试脚本能够包含一个或多个describe块,describe块称为 "测试套件",表示一组相关的测试,它是一个函数,有两个参数,第一个参数是测试套件的名称,第二个参数是一个实际执行的函数。
每一个describe块也能够包含一个或多个it块,it块称为 “测试用例",表示一个单独的测试,是测试的最小单位,它也是一个函数,第一个参数也是测试用例的名称,第二个参数是一个实际执行的函数,it块之间是同步运行的。
describe 和 it 大量嵌套后,就造成了一颗树。树的非叶子节点都是测试集合,叶子节点即 it ,就是测试用例。
注意,若是一个describe 里面没有 it (好比下面:), Mocha将不会执行这个 describe。
3.理解断言库
学习文档:http://www.chaijs.com
断言库能够理解为比较函数,也就是断言函数是否和预期一致,若是一致则表示测试经过,若是不一致表示测试失败。mocha自己是不包括断言库的,因此必须引入第三方断言库的,目前比较受欢迎的断言库有 should.js, expect.js, chai.
should.js是BDD风格
expect.js是expect风格的断言
//下面介绍的是chai
chai的expect(), assert() 和 should的断言
Mocha默认使用的是BDD的风格。expect和should都是BDD的风格,两者使用相同的链式语言来组织断言的,但不一样在于他们初始化断言的方式,expect使用
构造函数来建立断言对象实例,而should经过为 Object.prototype新增方法来实现断言(should不支持IE),expect直接指向 chai.expect,
should则是 chai.should();
上面的代码中 expect 是断言的意思,该做用是判断源码的实际执行结果与预期结果是否一致,若是不一致就抛出一个错误
三种的使用方法简单以下所示:
var expect = require('chai').expect;
expect(2).to.be.equal(2);
var assert = require('chai').
assert;
assert.equal((await contract.totalSupply()).toNumber(), 10);
var should = require('chai').should();
cookies.should.not.be.empty;
cookies.id.should.equal('10001','not equal to 10001');
所以在执行上面代码以前,
咱们须要在项目中安装 chai, 以下命令:
npm install --save-dev chai
1)var expect = require('chai').expect;
即引用 chai 断言库,使用的是 expect断言风格。
expect 官网API(http://chaijs.com/api/bdd/).
2)mocha测试代码如何运行?
上面的add.test.js 编写完成后,咱们须要运行测试代码了,进入add.test.js代码的目录后,执行以下命令可运行:
mocha add.test.js
mocha命令后面也能够指定多个文件,以下命令:
mocha xx.test.js yy.test.js
使用通配符:
或者咱们能够运行以下命令,执行多个测试脚本文件:
mocha spec/{add,reduce}.js //目录spec下的add.js和reduce.js文件
mocha spec/*.js //全部文件
mocha默认运行test子目录里面的测试脚本,咱们通常状况下,能够把测试脚本放在test目录下,而后进入test的上层目录,直接执行mocha命令便可:
mocha
而后你就会发现test目录下第一层的因此测试文件都被运行了。命令只会执行test第一层目录下全部文件,并不能执行嵌套目录下的文件。
为了执行全部嵌套目录下的文件,咱们能够 mocha命令后面加一个参数 --recursive 参数
更多的细节信息能够看:https://www.cnblogs.com/tugenhua0707/p/8419534.html,固然以后你要补充一下
3)测试用例的钩子
Mocha在describe块之中,提供了测试用例的四个钩子,before(), after(), beforeEach()和afterEach(),他们会在指定的时间内执行。
before(): 将会在全部测试用例执行以前运行,好比在以前插入数据等等操做。
after(): 会在全部测试执行以后运行,用于清理测试环境,回滚到清空数据状态。
beforeEach(): 将会在每一个测试用例执行以前执行,可用于测试测试须要准备相关数据的条件。
afterEach(): 将会在每一个测试用例以后执行,可用于准备测试用例所需的后置条件。
4)理解异步钩子函数
例子1:
var expect = require('chai').expect;
describe('异步钩子函数', function() {
var foo = false;
beforeEach(function(){
setTimeout(function(){
foo = true;
}, 50)
});
it('异步钩子函数成功', function() {
expect(foo).to.be.equal(true);
})
});
结果:
异步钩子函数
异步钩子函数成功:
AssertionError: expected false to equal true
+ expected - actual
-false
+true
如上能够看到测试失败,缘由是由于setTimeout 是异步的,在setTimeout执行完以前,it函数已经被执行了,因此foo当时数据仍是false,
所以false不等于true了。
这时候 done参数出来了,在回调函数存在时候,它会告诉mocha,你正在编写一个异步测试,会等到异步测试完成的时候来调用done函数,或者超过2秒后超时,以下代码就能够成功了;
var expect = require('chai').expect;
describe('异步钩子函数', function() {
var foo = false;
beforeEach(function(done){
setTimeout(function(){
foo = true;
// complete the async beforeEach
done();
}, 50)
});
it('异步钩子函数成功', function() {
expect(foo).to.be.equal(true);
});
});
(3)这篇文章不是要很详细地告诉你们概念性的内容,只是让有跟我同样想法(即学习了truffle框架后,发现里面的那个测试十分有意思,想以后作测试的时候也用这种测试方法),可是忽然不知道怎么入手的人一个方向,知道那是个什么测试框架,以及去哪里学习,以及一些比较简单的必须知道的概念和内容,知道这些其实你就能够写一个十分简单的测试例子了,建议结合别人的测试代码进行学习,如https://github.com/larvalabs/cryptopunks/tree/master/test
在下面进行测试:
1)一开始,当我想要直接复制truffle中写好的测试文件,进行小部分更改直接进行使用时,发现出错:
truffle中只须要这两句话就能够部署好合约,可是在没框架的状况下是不能够的
var testToken = artifacts.require(“./test-punk.sol”);
instance = await testToken.deployed(50);
这样会报错:
1.ReferenceError: artifacts is not defined
2.(function (exports, require, module, __filename, __dirname) { pragma solidity ^0.4.20;
^^^^^^^^
SyntaxError: Unexpected identifier
因此这就说明了在框架外是不能够这样子进行合约的编译的
经过上面咱们就可以知道truffle到使用上面两句部署指令前,还进行了编译compile和部署migrate,因此在测试前要将合约的编译和部署都弄好,你能够经过查看我写的remix的使用来学怎么使用remix进行编译和部署或者是看nodejs部署智能合约的方法来本身编写代码进行编译和部署,最终获得合约的部署地址NFMAddress
部署成功后,以后若是想在别的地方进行使用,须要如下几句话句话:
const NFMAbi = require("./testToken.json");//合约生成的Abi,通常为json文件
const NFMContract = web3.eth.contract(NFMAbi);
const instance = NFMContract.at(NFMAddress);
此时就可以调用该函数中的函数及变量了
2)其次,还要记得将相应的模块包下载下来,这里要添加package.json文件配置等内容
Error: Cannot find module ‘web3'
你安装的web3必定要放在本地的node_modules文件夹下,否则是读不出来的
3)使用nodejs来进行合约的编译和部署时,发现出现下面的错误
1.let abi = compiledContract.contracts['testToken'].interface;
出错:
TypeError: Cannot read property 'interface' of undefined
而后输出compiledContract进行查看
2.console.log(compiledContract);
发如今编译处就出错了
{ contracts: {},
errors:
[ ':49:17: ParserError: Expected identifier, got \'LParen\'\n constructor (uint number) public{\n ^\n' ],
sourceList: [ '' ],
sources: {} }
还有:
{ contracts: {},
errors:
[ ':26:9: TypeError: Wrong argument count for function call: 2 arguments given but expected 1.\n require(propertyValueToOwner[propertyValue] == 0x0,\'this is not the first-sell\');\n ^------------------------------------------------------------------------------^\n',
发现多是版本的问题,由于这里声明构造函数使用了新的声明方式,可是在这里没能被识别出。因此下载了新版本的solc,而后就成功了
4)
contract('testToken',async (accounts) => {
出错:ReferenceError: contract is not defined
由于contract是truffle框架弄的,不在框架中是不可以这样使用的,这时候想要使用帐号只能老实地链接区块链,经过web3模块去调用API接口
var Web3 = require("web3");
web3.setProvider(new Web3.providers.HttpProvider("http://localhost:8201"));
account1 = web3.eth.accounts[0];
5)最后运行结果也有问题:
用户deMBP:testToken 用户$ mocha test-mocha.js
testToken Test
check tokenNumber
1) deploy contract
2) sell token
3) buy token
4) check the balance
5) withdrawl the balance
0 passing (118ms)
5 failing
1) testToken Test
deploy contract:
TypeError: Cannot read property 'call' of undefined
at Context.<anonymous> (test-mocha.js:56:43)
2) testToken Test
sell token:
TypeError: Cannot read property 'sellToken' of undefined
at Context.<anonymous> (test-mocha.js:77:19)
3) testToken Test
buy token:
TypeError: Cannot read property 'testTokenIdToOwner' of undefined
at Context.<anonymous> (test-mocha.js:90:25)
4) testToken Test
check the balance:
TypeError: Cannot read property 'pendingDrawalOfUser' of undefined
at Context.<anonymous> (test-mocha.js:112:38)
5) testToken Test
withdrawl the balance:
TypeError: Cannot read property 'pendingDrawalOfUser' of undefined
at Context.<anonymous> (test-mocha.js:124:31)
Contract mined! address: 0x3cb4464f73eda60ac3ba1d46cd0544cd7ae18040 transactionHash: 0x62f27e3ccdfcb6ba54547107bbc8f1f9f152f0f588eddec9b1dc3a65d1d7d047
后面发现这个缘由是部署回调获得instance前,it函数中的内容就已经开始调用了,这样的话怎么着instance都是undefined的,那么确定是不可能可以调用合约中的函数的。并且在这里将部署函数放在了一个单独的it测试用例当中,可是下面的测试用例都是应该等待部署完成后才可以测试成功的,那么这样就不可能成功测试了,由于it测试用例是同步进行的,在部署的同时,其余测试用例也都开始运行了,⚠️有先后关系的测试内容应该要写在一个测试用例当中。
因此就不作在这以前进行部署的事情了,毕竟咱们确定在测试以前是已经把合约部署上去的了,那么只要用
let instance = MyContract.at('0x86757c9bdea10815e7d75a1577b6d9d2825dae0a');
这句话就好了
6)而后再运行也出错:
mocha Error: Timeout of 2000ms exceeded.
则须要你再运行的时候添加
mocha -t 20000 test.js
由于其默认的时间是2000ms,你的时间若是过大,能够进行本身设置
7)当你使用describe这些mocha的形式语句的时候,使用node test.js是不能运行成功的,会返回错误:
用户MBP:testToken 用户$ node test.js
/Users/用户/testToken/test.js:165
describe('testToken',function(){
^
ReferenceError: describe is not defined
因此必定要用mocha开头,mocha test.js
实现:
const debug = require("debug")("testToken"); const assert = require('assert') // var testToken = require("./test-punk.sol"); const Web3 = require('web3'); const web3 = new Web3(); web3.setProvider(new Web3.providers.HttpProvider('http://127.0.0.1:7545')); const fs = require("fs"); const solc = require("solc"); let source = fs.readFileSync("testToken.sol",'utf8');//read file let compiledContract = solc.compile(source,1);//compile // console.log(compiledContract); for (let contractName in compiledContract.contracts) { console.log("in"); var bytecode = compiledContract.contracts[contractName].bytecode; var abi = JSON.parse(compiledContract.contracts[contractName].interface); //将abi写成json形式 console.log("out"); } // 你要测试的是接口 // console.log(bytecode); // console.log(abi); let gasEstimate = web3.eth.estimateGas({data:'0x'+bytecode}); let MyContract = web3.eth.contract(abi); debug("deploying contract"); // let instance; let instance = MyContract.at('0x86757c9bdea10815e7d75a1577b6d9d2825dae0a');//可改 var user1 = web3.eth.accounts[0]; console.log(user1); var user2 = web3.eth.accounts[1]; var user3 = web3.eth.accounts[2]; var user4 = web3.eth.accounts[3]; var user5 = web3.eth.accounts[4]; var propertyValues = ['0x00000001','0x00000002','0x00000003','0x00000004','0x00000005']; var prices = [11,12,13,14,15]; var users = [user1,user2,user3,user4,user5]; //这个地方是想要在这里同时部署合约,可是发现老是不成功,部署异步老是太慢,before()函数好像也没有什么用,因此后面我也只能放弃部署了,只能先部署完获得地址再来调用了 // function deployContract() { // instance = MyContract.new(50,{from:user1,data:'0x'+bytecode,gas:47000000},function(e,contract){ // if(typeof contract.address !== 'undefined'){ // console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); // } // }); // } // before(deployContract); // var instance; describe("testToken Test",function(){ // it("deploy contract",async function(){ it("contract begin",async function(){ let num = propertyValues.length; // instance = await testToken.deployed(50); console.log(instance.address); console.log('check tokenNumber'); console.log(await instance.tokenNumber.call()); debug("create token");
//首先先建立5个token for(let i=0; i<num; i++){ await instance.create(propertyValues[i],prices[i],{from:users[i],value:prices[i],gas:30000000}); } debug("display token");
//而后查看生成的token的属性 let nextTokenId = await instance.nextTokenToCreate.call(); console.log("nextTokenId is :"+ nextTokenId); if(parseInt(nextTokenId) != 5){ console.log("initial token failed"); }else{ for(let i =0; i<num; i++){ console.log(await instance.propertyOfToken.call(i)); } } }); //而后sell token it("sell token",async function(){ await instance.sellToken(0,21,{from:user1,gas:30000000}); await instance.sellToken(1,22,{from:user2,gas:30000000}); //查看是否成功sell console.log(await instance.tokenIdToSell.call(0)); console.log(await instance.tokenIdToSell.call(1)); }); //其余用户对sell的token进行购买 it("buy token",async function(){
//查看购买前token的拥有者是谁 console.log(instance.testTokenIdToOwner.call(0)); console.log(instance.testTokenIdToOwner.call(1)); await instance.buyToken(0,{from:user3,value:21,gas:30000000}); await instance.buyToken(1,{from:user4,value:22,gas:30000000}); let buyer1 = await instance.testTokenIdToOwner.call(0); let buyer2 = await instance.testTokenIdToOwner.call(1); //查看购买后的拥有者是谁来核实购买成功进行 console.log(buyer1); console.log(buyer2); console.log("check the sell after buying"); console.log(await instance.tokenIdToSell.call(0)); console.log(await instance.tokenIdToSell.call(1)); assert.equal(buyer1,user3,"buying 0 is failed"); assert.equal(buyer2,user4,"buying 1 is failed"); }); //查看用户临时帐户中此时有多少积蓄 it("check the balance",async function(){ let user1Balance = await instance.pendingDrawalOfUser.call(user1); let user2Balance = await instance.pendingDrawalOfUser.call(user2); console.log(user1Balance); console.log(user2Balance); assert.equal(user1Balance,21,"user1 balance number is not right"); assert.equal(user2Balance,22,"user1 balance number is not right"); }); //而后将临时帐户中的钱转到钱包中 it("withdrawl the balance",async function(){ console.log(await instance.pendingDrawalOfUser.call(user1)); console.log(await instance.pendingDrawalOfUser.call(user2)); await instance.withdrawl({from:user1,gas:30000000}); await instance.withdrawl({from:user2,gas:30000000}); console.log(await instance.pendingDrawalOfUser.call(user1)); console.log(await instance.pendingDrawalOfUser.call(user2)); }); }); 结果是: 用户deMBP:testToken 用户$ mocha test-mocha.js in out 0x3455f15cc11f2e77c055f931a6c918ccc7c18fd8 testToken Test 0x86757c9bdea10815e7d75a1577b6d9d2825dae0a check tokenNumber BigNumber { s: 1, e: 1, c: [ 50 ] } nextTokenId is :5
//生成的token的属性 [ BigNumber { s: 1, e: 0, c: [ 0 ] }, '0x0000000100000000000000000000000000000000000000000000000000000000', '0x3455f15cc11f2e77c055f931a6c918ccc7c18fd8', BigNumber { s: 1, e: 1, c: [ 11 ] } ] [ BigNumber { s: 1, e: 0, c: [ 1 ] }, '0x0000000200000000000000000000000000000000000000000000000000000000', '0x7ddad6a67544efb0c51808c77009a7b98cc81630', BigNumber { s: 1, e: 1, c: [ 12 ] } ] [ BigNumber { s: 1, e: 0, c: [ 2 ] }, '0x0000000300000000000000000000000000000000000000000000000000000000', '0xe9478ebcf4c755ad945a351261c8fa046672963b', BigNumber { s: 1, e: 1, c: [ 13 ] } ] [ BigNumber { s: 1, e: 0, c: [ 3 ] }, '0x0000000400000000000000000000000000000000000000000000000000000000', '0x920f422b761976972a9eadbec1f5341a9747ea6a', BigNumber { s: 1, e: 1, c: [ 14 ] } ] [ BigNumber { s: 1, e: 0, c: [ 4 ] }, '0x0000000500000000000000000000000000000000000000000000000000000000', '0xa17a7fa74a7dd57dff005b45234292e7daaf150c', BigNumber { s: 1, e: 1, c: [ 15 ] } ] ✓ contract begin (1541ms)
// [ true, '0x0000000100000000000000000000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 0 ] }, '0x3455f15cc11f2e77c055f931a6c918ccc7c18fd8', BigNumber { s: 1, e: 1, c: [ 21 ] } ] [ true, '0x0000000200000000000000000000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 1 ] }, '0x7ddad6a67544efb0c51808c77009a7b98cc81630', BigNumber { s: 1, e: 1, c: [ 22 ] } ] ✓ sell token (663ms) 0x3455f15cc11f2e77c055f931a6c918ccc7c18fd8 0x7ddad6a67544efb0c51808c77009a7b98cc81630 0xe9478ebcf4c755ad945a351261c8fa046672963b 0x920f422b761976972a9eadbec1f5341a9747ea6a check the sell after buying [ false, '0x0000000100000000000000000000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 0 ] }, '0x0000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 0 ] } ] [ false, '0x0000000200000000000000000000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 1 ] }, '0x0000000000000000000000000000000000000000', BigNumber { s: 1, e: 0, c: [ 0 ] } ] ✓ buy token (954ms) BigNumber { s: 1, e: 1, c: [ 21 ] } BigNumber { s: 1, e: 1, c: [ 22 ] } ✓ check the balance (206ms) BigNumber { s: 1, e: 1, c: [ 21 ] } BigNumber { s: 1, e: 1, c: [ 22 ] } BigNumber { s: 1, e: 0, c: [ 0 ] } BigNumber { s: 1, e: 0, c: [ 0 ] } ✓ withdrawl the balance (631ms) 5 passing (4s)
注意:在这里看好像这个例子也可以顺序执行,可是后面发现上面例子的因此it应该合成一个it来写,否则顺序是不必定能保证的,由于it测试用例在运行时是同步运行的,可是我这里的测试用例实际上是有但愿它按照顺序来运行,因此改成:
describe("testToken Test",function(){ it("contract begin",async function(){ let num = propertyValues.length; // instance = await testToken.deployed(50); console.log(instance.address); console.log('check tokenNumber'); console.log(await instance.tokenNumber.call()); debug("create token"); //首先先建立5个token for(let i=0; i<num; i++){ await instance.create(propertyValues[i],prices[i],{from:users[i],value:prices[i],gas:30000000}); } debug("display token"); //而后查看生成的token的属性 let nextTokenId = await instance.nextTokenToCreate.call(); console.log("nextTokenId is :"+ nextTokenId); if(parseInt(nextTokenId) != 5){ console.log("initial token failed"); }else{ for(let i =0; i<num; i++){ console.log(await instance.propertyOfToken.call(i)); } } //而后sell token await instance.sellToken(0,21,{from:user1,gas:30000000}); await instance.sellToken(1,22,{from:user2,gas:30000000}); //查看是否成功sell console.log(await instance.tokenIdToSell.call(0)); console.log(await instance.tokenIdToSell.call(1)); //其余用户对sell的token进行购买 //查看购买前token的拥有者是谁 console.log(instance.testTokenIdToOwner.call(0)); console.log(instance.testTokenIdToOwner.call(1)); await instance.buyToken(0,{from:user3,value:21,gas:30000000}); await instance.buyToken(1,{from:user4,value:22,gas:30000000}); let buyer1 = await instance.testTokenIdToOwner.call(0); let buyer2 = await instance.testTokenIdToOwner.call(1); //查看购买后的拥有者是谁来核实购买成功进行 console.log(buyer1); console.log(buyer2); console.log("check the sell after buying"); console.log(await instance.tokenIdToSell.call(0)); console.log(await instance.tokenIdToSell.call(1)); assert.equal(buyer1,user3,"buying 0 is failed"); assert.equal(buyer2,user4,"buying 1 is failed"); //查看用户临时帐户中此时有多少积蓄 let user1Balance = await instance.pendingDrawalOfUser.call(user1); let user2Balance = await instance.pendingDrawalOfUser.call(user2); console.log(user1Balance); console.log(user2Balance); assert.equal(user1Balance,21,"user1 balance number is not right"); assert.equal(user2Balance,22,"user1 balance number is not right"); //而后将临时帐户中的钱转到钱包中 console.log(await instance.pendingDrawalOfUser.call(user1)); console.log(await instance.pendingDrawalOfUser.call(user2)); await instance.withdrawl({from:user1,gas:30000000}); await instance.withdrawl({from:user2,gas:30000000}); console.log(await instance.pendingDrawalOfUser.call(user1)); console.log(await instance.pendingDrawalOfUser.call(user2)); }); });