快速學習-mocha 簡介與入門

mocha 簡介

mocha 是 JavaScript 的一個單元測試框架,既可以在瀏覽器環境中運行,也可以在 node.js 環境下運行。我們只需要編寫測試用例,mocha 會將測試自動運行並給出測試結果。

mocha 的主要特點有:

  • 既可以測試簡單的 JavaScript 函數,又可以測試異步代碼;  可以自動運行所有測試,也可以只運行特定的測試;
  • 可以支持 before、after、beforeEach 和 afterEach 來編寫初始化代碼。

測試腳本示例

假設我們編寫了一個 sum.js,並且輸出一個簡單的求和函數:

module.exports = function(...rest) {
	var sum = 0;
	for (let n of rest) {
		sum += n;
	}
	return sum;
};

這個函數非常簡單,就是對輸入的任意參數求和並返回結果。

如果我們想對這個函數進行測試,可以寫一個 test.js,然後使用 Node.js 提供的 assert 模塊進行斷言:

const assert = require('assert');
const sum = require('./sum');
assert.strictEqual(sum(), 0);
assert.strictEqual(sum(1), 1);
assert.strictEqual(sum(1, 2), 3);
assert.strictEqual(sum(1, 2, 3), 6);

assert 模塊非常簡單,它斷言一個表達式爲 true。如果斷言失敗,就拋出Error。

單獨寫一個 test.js 的缺點是沒法自動運行測試,而且,如果第一個 assert報錯,後面的測試也執行不了了。

如果有很多測試需要運行,就必須把這些測試全部組織起來,然後統一執行,並且得到執行結果。這就是我們爲什麼要用 mocha 來編寫並運行測試。

我們利用 mocha 修改後的測試腳本如下:

const assert = require('assert');
const sum = require('../sum');
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);
				});
			});
});

這裏我們使用 mocha 默認的 BDD-style 的測試。describe 可以任意嵌套,以便把相關測試看成一組測試。

describe 可以任意嵌套,以便把相關測試看成一組測試;而其中的每個 it就代表一個測試。

每個 it(“name”, function() {…})就代表一個測試。例如,爲了測試 sum(1, 2),我們這樣寫:

it('sum(1, 2) should return 3', () => {
	assert.strictEqual(sum(1, 2), 3);
});

編寫測試的原則是,一次只測一種情況,且測試代碼要非常簡單。我們編寫多個測試來分別測試不同的輸入,並使用 assert 判斷輸出是否是我們所期望的。

運行測試腳本

下一步,我們就可以用 mocha 運行測試了。打開命令提示符,切換到項目目錄,然後創建文件夾 test,將 test.js 放入 test 文件夾下,執行命令:

./node_modules/mocha/bin/mocha

mocha 就會自動執行 test 文件夾下所有測試,然後輸出如下:

#sum.js
 #sum()sum() should return 0sum(1) should return 1sum(1, 2) should return 3sum(1, 2, 3) should return 6
 4 passing (7ms)

這說明我們編寫的 4 個測試全部通過。如果沒有通過,要麼修改測試代碼,要麼修改 hello.js,直到測試全部通過爲止。

編寫合約測試腳本

測試時我們通常會把每次測試運行的環境隔離開,以保證互不影響。對應到合約測試,我們每次測試都需要部署新的合約實例,然後針對新的實例做功能測試。 Car 合約的功能比較簡單,我們只要設計 2 個測試用例:

  • 合約部署時傳入的 brand 屬性被正確存儲;
  • 調用 setBrand 之後合約的 brand 屬性被正確更新;
    新建測試文件 tests/car.spec.js,完整的測試代碼如下。
const path = require('path'); 
const assert = require('assert'); 
const ganache = require('ganache-cli'); 
const Web3 = require('web3'); 
// 1. 配置 provider 
const web3 = new Web3(ganache.provider()); 
// 2. 拿到 abi 和 bytecode 
const contractPath = path.resolve(__dirname, 
'../compiled/Car.json'); 
const { interface, bytecode } = require(contractPath); 
let accounts; 
let contract; 
const initialBrand = 'BMW';
describe('contract', () => {
	// 3. 每次跑單測時需要部署全新的合約實例,起到隔離的作用
	beforeEach(async() => {
		accounts = await web3.eth.getAccounts();
		console.log('合約部署賬戶:', accounts[0]);
		contract = await new
		web3.eth.Contract(JSON.parse(interface))
			.deploy({
				data: bytecode,
				arguments: [initialBrand]
			})
			.send({
				from: accounts[0],
				gas: '1000000'
			});
		console.log('合約部署成功:',
			contract.options.address);
	});
	// 4. 編寫單元測試
	it('deployed contract', () => {
		assert.ok(contract.options.address);
	});
	it('should has initial brand', async() => {
		const brand = await contract.methods.brand().call();
		assert.equal(brand, initialBrand);
	});
	it('can change the brand', async() => {
		const newBrand = 'Benz';
		await contract.methods.setBrand(newBrand)
			.send({
				from: accounts[0]
			});
		const brand = await contract.methods.brand().call();
		assert.equal(brand, newBrand);
	});
});

整個測試代碼使用的斷言庫是 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。

運行測試腳本

有了測試代碼,就可以運行並觀察結果。mocha 默認會執行 test 目錄下的所有腳本,但我們也可以傳入腳本路徑,指定執行目錄。如果你環境中全局安裝了 mocha,可以使用如下命令運行測試:

mocha tests

如果沒有全局安裝 mocha,就使用如下命令運行測試:

./node_modules/.bin/mocha tests

如果一切正常,我們可以看到這樣的輸出結果:
在這裏插入圖片描述

完整的工作流

到目前爲止,我們已經熟悉了智能合約的開發、編譯、部署、測試,而在實際工作中,把這些過程串起來才能算作是真正意義上的工作流。比如修改了合約代碼需要重新運行測試,但是重新運行測試之前需要重新編譯,而部署的過程也是類似的,每次部署的都要是最新的合約代碼。

通過 npm script 機制,我們可以把智能合約的工作流串起來,讓能自動化的儘可能自動化,在 package.json 中作如下修改:

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

上面的改動中,我們爲項目增加了 3 條命令:compile、test、deploy,其中 pretest、predeploy 是利用了 npm script 的生命週期機制,把我們的compile、test、deploy 串起來。

接下來我們可以使用 npm run test 運行測試,結果如下:
在這裏插入圖片描述
同理我們可以使用 npm run deploy 部署合約,結果如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章