mocha
在第一小結(jié)中的測(cè)試中用到了 mocha 框架津函,這一節(jié)就說(shuō)說(shuō) mocha 框架吧屡久。下面整理的內(nèi)容主要來(lái)源于官網(wǎng)温眉,如需了解更多請(qǐng)移步mocha 官網(wǎng)惭等。
mocha 是一個(gè)功能豐富的前端測(cè)試框架苫费,mocha 既可以基于 Node.js 環(huán)境運(yùn)行也可以在瀏覽器環(huán)境運(yùn)行項(xiàng)目地址汤锨。
Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun. Mocha tests run serially, allowing for flexible and accurate reporting, while mapping uncaught exceptions to the correct test cases. Hosted on GitHub.
安裝
安裝有兩種方式:1. 全局安裝;2. 將 mocha 作為項(xiàng)目依賴(lài)模塊安裝百框。
npm install --global mocha
npm install --save-dev mocha
起步
測(cè)試腳本的寫(xiě)法
Mocha 的作用是運(yùn)行測(cè)試腳本闲礼,首先必須學(xué)會(huì)寫(xiě)測(cè)試腳本。所謂"測(cè)試腳本",就是用來(lái)測(cè)試源碼的腳本柬泽。下面是一個(gè)加法模塊 add.js 的代碼慎菲。
// add.js
function add(x, y) {
return x + y;
}
module.exports = add;
要測(cè)試這個(gè)加法模塊是否正確,就要寫(xiě)測(cè)試腳本聂抢。通常钧嘶,測(cè)試腳本與所要測(cè)試的源碼腳本同名,但是后綴名為.test.js(表示測(cè)試)或者.spec.js(表示規(guī)格)琳疏。比如有决,add.js 的測(cè)試腳本名字就是 add.test.js。
// add.test.js
var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函數(shù)的測(cè)試', function() {
it('1 加 1 應(yīng)該等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
});
上面這段代碼空盼,就是測(cè)試腳本书幕,它可以獨(dú)立執(zhí)行。測(cè)試腳本里面應(yīng)該包括一個(gè)或多個(gè) describe 塊揽趾,每個(gè) describe 塊應(yīng)該包括一個(gè)或多個(gè) it 塊台汇。
describe 塊稱(chēng)為"測(cè)試套件"(test suite),表示一組相關(guān)的測(cè)試篱瞎。它是一個(gè)函數(shù)苟呐,第一個(gè)參數(shù)是測(cè)試套件的名稱(chēng)("加法函數(shù)的測(cè)試"),第二個(gè)參數(shù)是一個(gè)實(shí)際執(zhí)行的函數(shù)俐筋。
it 塊稱(chēng)為"測(cè)試用例"(test case)牵素,表示一個(gè)單獨(dú)的測(cè)試,是測(cè)試的最小單位澄者。它也是一個(gè)函數(shù)笆呆,第一個(gè)參數(shù)是測(cè)試用例的名稱(chēng)("1 加 1 應(yīng)該等于 2"),第二個(gè)參數(shù)是一個(gè)實(shí)際執(zhí)行的函數(shù)粱挡。
斷言庫(kù)
上面的測(cè)試腳本里面赠幕,有一句斷言。
expect(add(1, 1)).to.be.equal(2);
所謂"斷言"询筏,就是判斷源碼的實(shí)際執(zhí)行結(jié)果與預(yù)期結(jié)果是否一致榕堰,如果不一致就拋出一個(gè)錯(cuò)誤。上面這句斷言的意思是嫌套,調(diào)用 add(1, 1)逆屡,結(jié)果應(yīng)該等于 2。所有的測(cè)試用例(it 塊)都應(yīng)該含有一句或多句的斷言灌危。它是編寫(xiě)測(cè)試用例的關(guān)鍵康二。斷言功能由斷言庫(kù)來(lái)實(shí)現(xiàn),Mocha 本身不帶斷言庫(kù)勇蝙,所以必須先引入斷言庫(kù)沫勿。
var expect = require('chai').expect;
斷言庫(kù)有很多種挨约,Mocha 并不限制使用哪一種,它允許你使用你想要的任何斷言庫(kù)产雹。上面代碼引入的斷言庫(kù)是 chai诫惭,并且指定使用它的 expect 斷言風(fēng)格。下面這些常見(jiàn)的斷言庫(kù):
- assert 這個(gè)是 Node.js 中的斷言模塊蔓挖。
- should.js
- expect.js
- chaijs
- better-assert
- unexpected.js
expect 斷言的優(yōu)點(diǎn)是很接近自然語(yǔ)言夕土,下面是一些例子。
// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });
// 布爾值為true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;
// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;
// match
expect('foobar').to.match(/^foo/);
基本上瘟判,expect 斷言的寫(xiě)法都是一樣的怨绣。頭部是 expect 方法,尾部是斷言方法拷获,比如 equal篮撑、a/an、ok匆瓜、match 等赢笨。兩者之間使用 to 或 to.be 連接。如果 expect 斷言不成立驮吱,就會(huì)拋出一個(gè)錯(cuò)誤茧妒。事實(shí)上,只要不拋出錯(cuò)誤左冬,測(cè)試用例就算通過(guò)桐筏。
it('1 加 1 應(yīng)該等于 2', function() {});
上面的這個(gè)測(cè)試用例,內(nèi)部沒(méi)有任何代碼又碌,由于沒(méi)有拋出了錯(cuò)誤九昧,所以還是會(huì)通過(guò)绊袋。
在命令行中使用 mocha
在命令行使用 mocha 則需要在全局安裝:npm install mocha -g
, 可以通過(guò)一些參數(shù)來(lái)測(cè)試指定的文件毕匀、指定展示結(jié)果的風(fēng)格、導(dǎo)出測(cè)試報(bào)告等癌别≡聿恚可使用mocha --help
命令查看所有參數(shù)。
在項(xiàng)目中使用 mocha
初始化一個(gè) node 項(xiàng)目
mkdir step1 && cd step1
npm init -y
npm install --save-dev mocha
創(chuàng)建一個(gè)文件夾 test, 并在里面創(chuàng)建一個(gè) test.js ,寫(xiě)入下面的內(nèi)容展姐。
下面這段代碼主要是簡(jiǎn)單測(cè)試了一下數(shù)組 [1, 2, 3]
的 indexOf()
方法躁垛。預(yù)期[1, 2, 3].indexOf(4)
返回-1。
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1, 2, 3].indexOf(4), -1);
});
});
});
修改 package.json 文件圾笨。
{
"scripts": {
"test": "mocha"
}
}
然后打開(kāi)終端教馆,在命令行中運(yùn)行npm run test
, 下面是運(yùn)行結(jié)果。
mocha 基礎(chǔ)用法
測(cè)試回調(diào)方法被多次調(diào)用
如果使用基于回調(diào)的異步測(cè)試擂达,如果 done()被多次調(diào)用土铺,則 Mocha 將拋出錯(cuò)誤。這對(duì)于捕捉意外的雙重回調(diào)很方便。
it('double done', function(done) {
// Calling `done()` twice is an error
setImmediate(done);
setImmediate(done);
});
回調(diào)多次意外運(yùn)行錯(cuò)誤結(jié)果
異步代碼檢查
使用 Mocha 測(cè)試異步代碼非常簡(jiǎn)單悲敷!只需在測(cè)試完成時(shí)調(diào)用回調(diào)究恤。通過(guò)向它添加一個(gè)回調(diào)函數(shù)(通常名為 done),Mocha 會(huì)知道它應(yīng)該等待這個(gè)函數(shù)被調(diào)用來(lái)完成測(cè)試后德。該回調(diào)接受 Error 實(shí)例(或其子類(lèi))或偽造值;其他任何事情都會(huì)導(dǎo)致測(cè)試失敗部宿。
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(function(err) {
if (err) done(err);
else done();
});
});
});
});
為了使事情更簡(jiǎn)單,done()回調(diào)函數(shù)也接受一個(gè) Error 實(shí)例(即新的 Error())瓢湃,所以我們可以直接使用它:
describe('User', function() {
describe('#save()', function() {
it('should save without error', function(done) {
var user = new User('Luna');
user.save(done);
});
});
});
運(yùn)行 Promise
或者理张,您可以不使用 done()回調(diào),而是返回一個(gè) Promise绵患。如果您正在測(cè)試的 API 返回 Promise 而不是回調(diào)涯穷,這很有用:
beforeEach(function() {
return db.clear().then(function() {
return db.save([tobi, loki, jane]);
});
});
describe('#find()', function() {
it('respond with matching records', function() {
return db.find({ type: 'User' }).should.eventually.have.length(3);
});
});
使用 async/await
如果您的 JS 環(huán)境支持異步/等待,您也可以編寫(xiě)像這樣的異步測(cè)試:
beforeEach(async function() {
await db.clear();
await db.save([tobi, loki, jane]);
});
describe('#find()', function() {
it('responds with matching records', async function() {
const users = await db.find({ type: 'User' });
users.should.have.length(3);
});
});
測(cè)試同步的代碼
在測(cè)試同步代碼時(shí)藏雏,省略回調(diào)拷况,mocha 將自動(dòng)繼續(xù)下一次測(cè)試。
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
[1, 2, 3].indexOf(5).should.equal(-1);
[1, 2, 3].indexOf(0).should.equal(-1);
});
});
});
箭頭函數(shù)
不鼓勵(lì)在 mocha 中使用箭頭函數(shù)掘殴。 表達(dá)式中綁定的 this赚瘦,不能訪問(wèn) mocha 上下文。例如奏寨,以下代碼將失斊鹨狻:
describe('my suite', () => {
it('my test', () => {
// should set the timeout of this test to 1000 ms; instead will fail
this.timeout(1000);
assert.ok(true);
});
});
如果你不需要使用 mocha 的上下文,表達(dá)式是可以正常運(yùn)行的病瞳。但是揽咕,如果最終需要重構(gòu),結(jié)果可能會(huì)與預(yù)期有所不同套菜。
鉤子
憑借其默認(rèn)的“BDD”風(fēng)格界面亲善,Mocha 提供了 before(),after()逗柴,beforeEach()和 afterEach()之前的鉤子蛹头。這些應(yīng)該用于設(shè)置先決條件并在測(cè)試后進(jìn)行清理。
describe('hooks', function() {
before(function() {
// runs before all tests in this block
});
after(function() {
// runs after all tests in this block
});
beforeEach(function() {
// runs before each test in this block
});
afterEach(function() {
// runs after each test in this block
});
// test cases
});
測(cè)試可以在你的鉤子之前戏溺,之后或穿插出現(xiàn)渣蜗。視情況而定,掛鉤將按其定義的順序運(yùn)行;所有 before()
鉤子運(yùn)行(一次)旷祸,然后任何 beforeEach()
鉤子耕拷,測(cè)試,任何 afterEach()
鉤子托享,以及最后的 after()
鉤子(一次)骚烧。
鉤子描述
任何鉤子都可以通過(guò)可選的描述來(lái)調(diào)用控淡,從而更容易查明測(cè)試中的錯(cuò)誤。如果鉤子被賦予了一個(gè)命名函數(shù)止潘,那么如果沒(méi)有提供描述掺炭,將使用該名稱(chēng)。
beforeEach(function() {
// beforeEach hook
});
beforeEach(function namedFun() {
// beforeEach:namedFun
});
beforeEach('some description', function() {
// beforeEach:some description
});
異步鉤子
所有鉤子(before(), after(), beforeEach(), afterEach())都可能是同步或異步的凭戴,其行為與常規(guī)測(cè)試案例非常相似涧狮。例如下面的代碼,您可能希望在每次測(cè)試之前用虛擬內(nèi)容填充數(shù)據(jù)庫(kù):
describe('Connection', function() {
var db = new Connection(),
tobi = new User('tobi'),
loki = new User('loki'),
jane = new User('jane');
// 每次測(cè)試前填充數(shù)據(jù)
beforeEach(function(done) {
db.clear(function(err) {
if (err) return done(err);
db.save([tobi, loki, jane], done);
});
});
describe('#find()', function() {
it('respond with matching records', function(done) {
db.find({ type: 'User' }, function(err, res) {
if (err) return done(err);
res.should.have.length(3);
done();
});
});
});
});
root 級(jí)別的鉤子
你也可以選擇任何文件并添加“root”級(jí)別的鉤子么夫。例如者冤,在所有 describe()塊之外添加 beforeEach()。這里定義的回調(diào) beforeEach()在任何測(cè)試用例之前運(yùn)行档痪,而不管它存在于哪個(gè)文件中(這是因?yàn)?Mocha 有一個(gè)隱含的 describe()塊涉枫,稱(chēng)為“root 套件”)。
beforeEach(function() {
console.log('before every test in every file');
});
延遲執(zhí)行 root 套件
如果您需要在運(yùn)行任何套件之前執(zhí)行異步操作腐螟,則可能會(huì)延遲 root 套件愿汰。用--delay 標(biāo)志運(yùn)行 mocha。這將在全局上下文中附加一個(gè)特殊的回調(diào)函數(shù) run():
setTimeout(function() {
// do some setup
describe('my suite', function() {
// ...
});
run();
}, 5000);
待定的測(cè)試用例
待定的測(cè)試用例就是指的那些最終需要完成而待完成測(cè)試的用例乐纸,這些實(shí)例只有描述而沒(méi)有會(huì)回調(diào)衬廷。待測(cè)試將包含在測(cè)試結(jié)果中,并標(biāo)記為待處理汽绢。未決測(cè)試不被視為失敗測(cè)試吗跋。如下面這種:
describe('Array', function() {
describe('#indexOf()', function() {
// pending test below
it('should return -1 when the value is not present');
});
});
運(yùn)行結(jié)果
測(cè)試用例管理
當(dāng)有很多測(cè)試用例。有時(shí)宁昭,我們希望只運(yùn)行其中的幾個(gè)跌宛,這時(shí)可以用 only 方法。describe 塊和 it 塊都允許調(diào)用 only 方法积仗,表示只運(yùn)行某個(gè)測(cè)試套件或測(cè)試用例疆拘。
describe('Array', function() {
describe.only('#indexOf()', function() {
// ...
});
});
describe('Array', function() {
describe('#indexOf()', function() {
it.only('should return -1 unless present', function() {
// ...
});
it('should return the index when present', function() {
// ...
});
});
});
有時(shí)需要跳過(guò)一些測(cè)試用例可以使 skip 方法:
describe('Array', function() {
describe.skip('#indexOf()', function() {
// ...
});
});
describe('Array', function() {
describe('#indexOf()', function() {
it.skip('should return -1 unless present', function() {
// this test will not be run
});
it('should return the index when present', function() {
// this test will be run
});
});
});
有時(shí)需要根據(jù)環(huán)境來(lái)判斷是否跳過(guò)或者指定運(yùn)行一些實(shí)例,可以參考下面的代碼
it('should only test in the correct environment', function() {
if (/* check test environment */) {
// make assertions
} else {
this.skip();
}
});
it('should only test in the correct environment', function() {
if (/* check test environment */) {
// make assertions
} else {
// do nothing
}
});
before(function() {
if (/* check test environment */) {
// setup code
} else {
this.skip();
}
});