單元測(cè)試
定義: 單元測(cè)試是用來對(duì)一個(gè)模塊、一個(gè)函數(shù)或者一個(gè)類來進(jìn)行正確性檢驗(yàn)的測(cè)試工作。
必要性: 在實(shí)際使用過程中會(huì)伴隨著一大批的附帶操作大量增加測(cè)試時(shí)間,并且無法保證其測(cè)試覆蓋率。單元測(cè)試的目的并不僅僅是確認(rèn)是否可用,而是更高效更穩(wěn)定的確認(rèn)其是否可用。一個(gè)函數(shù)在多個(gè)組件中被調(diào)用,但是由于當(dāng)前組件的特殊性需要對(duì)函數(shù)進(jìn)行加工,加工之后對(duì)原來的功能影響是未知的,如果一一將引用函數(shù)的功能全部測(cè)一遍,太浪費(fèi)時(shí)間了,單元測(cè)試解決了重復(fù)測(cè)試相同函數(shù)不同功能的測(cè)試時(shí)間。測(cè)試不通過,根據(jù)需要,要么修改代碼,要么修改測(cè)試
意義: 這種以測(cè)試為驅(qū)動(dòng)的開發(fā)模式(TDD)最大的好處就是確保一個(gè)程序模塊的行為符合我們?cè)O(shè)計(jì)的測(cè)試用例。在將來修改的時(shí)候,可以極大程度地保證該模塊行為仍然是正確的
命名規(guī)則
新建測(cè)試腳本 calcu.test.js,一般命名規(guī)則測(cè)試腳本和原腳本同名,但是后綴名為.test.js (.spec.js)
測(cè)試腳本的樣子
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塊稱為"測(cè)試套件"(test suite),表示一組相關(guān)的測(cè)試。它是一個(gè)函數(shù),第一個(gè)參數(shù)是測(cè)試套件的名稱("加法函數(shù)的測(cè)試"),第二個(gè)參數(shù)是一個(gè)實(shí)際執(zhí)行的函數(shù)。
it塊稱為"測(cè)試用例"(test case),表示一個(gè)單獨(dú)的測(cè)試,是測(cè)試的最小單位。它也是一個(gè)函數(shù),第一個(gè)參數(shù)是測(cè)試用例的名稱("1 加 1 應(yīng)該等于 2"),第二個(gè)參數(shù)是一個(gè)實(shí)際執(zhí)行的函數(shù)。
所謂"斷言",就是判斷源碼的實(shí)際執(zhí)行結(jié)果與預(yù)期結(jié)果是否一致,如果不一致就拋出一個(gè)錯(cuò)誤。上面這句斷言的意思是,調(diào)用add(1, 1),結(jié)果應(yīng)該等于2
一個(gè)單元測(cè)試?yán)锩婵梢园鄠€(gè)斷言語句
常見的測(cè)試類型
常規(guī)函數(shù)的測(cè)試
異步函數(shù)的測(cè)試
api測(cè)試
- 常規(guī)
export.add = (a,b) => {
return a + b;
}
// 測(cè)試腳本如下 calcu.test.js
let calcu = require('./calcu');
let should = require('should');
describe('add func test', () => {
calcu.add(2,2).should.equal(4);
})
// 運(yùn)行
mocha calcu.test.js
- 異步
// 新建book.js
let fs = require('fs);
export.read = (cb) => {
fs.readFile('./book.txt', 'utf-8', (err, result) => {
if(err) throw err;
cb(null, result);
})
}
// 新建文件book.test.js
let book = require('./book');
let expect = require('chai').expect;
describe('async', () => {
it('read book async', (done) => {
book.read((err, result) => {
expect(err).equal(null);
expect(result).to.be.a('string');
done(); // 告訴mocha測(cè)試結(jié)束
})
})
})
// 運(yùn)行
mocha book.test.js
// 運(yùn)行mocha book.test.js,我們會(huì)發(fā)現(xiàn)成功了,但是如果我們把book.js增加一個(gè)定時(shí)函數(shù),改為如下例子
let fs = require('fs');
exports.read = (cb) => {
setTimeout(function() {
fs.readFile('./book.txt', 'utf-8', (err, result) => {
if (err) return cb(err);
console.log("result",result);
cb(null, result);
})
}, 3000);
}
// 會(huì)發(fā)現(xiàn)報(bào)如下錯(cuò)誤
// Timeout of 2000ms exceeded.
這是因?yàn)閙ocha默認(rèn)每個(gè)測(cè)試用例最多執(zhí)行2000
毫秒,如果到時(shí)沒有得到結(jié)果,就報(bào)錯(cuò)。所以我們?cè)谶M(jìn)行異步操作的時(shí)候,需要額外指定timeout時(shí)間
mocha --timeout 5000 book.test.js
// 指定了超時(shí)時(shí)間是5秒鐘
- api測(cè)試
- api 測(cè)試需要用到一個(gè)模塊是supertest
// 安裝
npm intall supertest --save-dev
// 新建文件 api.test.js
let expect = require('chai').expext;
let request = require("supertest");
describe('api', () => {
it('get baidu information', function (done) {
request('https://www.baidu.com')
.get('/')
.expect(200)
.expect('Content-Type', /html/)
.end(function (err, res){
expect(res).to.be.an('object');
done();
})
})
})
命令行參數(shù)詳解
–reporter :用來指定報(bào)告的格式 默認(rèn)spec 可以另外安裝網(wǎng)頁格式
-t 5000是因?yàn)槲覀儨y(cè)試用例中有一個(gè)異步執(zhí)行過程,需要調(diào)高mocha的單元測(cè)試時(shí)間
–watch :參數(shù)用來監(jiān)視指定的測(cè)試腳本。只要測(cè)試腳本有變化
–bail:參數(shù)指定只要有一個(gè)測(cè)試用例沒有通過,就停止執(zhí)行后面的測(cè)試用例
–grep:參數(shù)用來搜索單元測(cè)試用例的名稱,然后運(yùn)行符合搜索條件的測(cè)試用例,支持正則表達(dá)
–invert:參數(shù)表示只運(yùn)行不符合條件的測(cè)試腳本,必須與–grep參數(shù)配合使用。
--recursive 一般如果運(yùn)行mocha,會(huì)執(zhí)行當(dāng)前目錄下的test目錄的一級(jí)層級(jí)的所有js文件,但是test下的更多層級(jí)卻沒辦法運(yùn)行,這時(shí)就需要參數(shù)–recursive,這時(shí)test子目錄下面所有的測(cè)試用例----不管在哪一層----都會(huì)執(zhí)行
配置文件mocha.opts的配置
- 每次我們運(yùn)行測(cè)試用例的時(shí)候都需要寫很長(zhǎng)一段命令行,每次都一樣,這樣是不可取的,所以我們可以把這些配置維護(hù)到配置文件里面 (https://cnodejs.org/topic/59e3873520a1a3647d72ac39)
mocha的生命鉤子
- mocha一共四個(gè)生命鉤子
before():在該區(qū)塊的所有測(cè)試用例之前執(zhí)行
after():在該區(qū)塊的所有測(cè)試用例之后執(zhí)行
beforeEach():在每個(gè)單元測(cè)試前執(zhí)行
和afterEach():在每個(gè)單元測(cè)試后執(zhí)行
typescript規(guī)定了數(shù)據(jù)的類型 測(cè)試解決了過程和結(jié)果的正確性
目錄的結(jié)構(gòu)
函數(shù)寫法要求規(guī)范
斷言的框架
測(cè)試框架解決問題?異同
dom
單元測(cè)試中應(yīng)該避免
太多的條件邏輯
構(gòu)造函數(shù)中做了太多事情
太多的全局變量
太多的靜態(tài)方法
過多外部依賴
無關(guān)邏輯
測(cè)試代碼時(shí),只考慮測(cè)試,不考慮內(nèi)部實(shí)現(xiàn)
數(shù)據(jù)盡量模擬現(xiàn)實(shí),越靠近現(xiàn)實(shí)越好
充分考慮數(shù)據(jù)的邊界條件
對(duì)重點(diǎn)、復(fù)雜、核心代碼,重點(diǎn)測(cè)試
利用AOP(beforeEach、afterEach),減少測(cè)試代碼數(shù)量,避免無用功能
測(cè)試、功能開發(fā)相結(jié)合,有利于設(shè)計(jì)和代碼重構(gòu)
測(cè)試框架的必要性
就像vue開發(fā)和原生開發(fā)一樣 原生開發(fā)隨著功能的不斷增加,代碼的維護(hù)和功能代碼的添加變得越來越困難
測(cè)試代碼也一樣 單元測(cè)試也需要一種行之有效的實(shí)踐來確保其質(zhì)量和可維護(hù)性。
測(cè)試框架 | 簡(jiǎn)介 | 優(yōu)點(diǎn) | 不足 |
---|---|---|---|
QUnit | QUnit是jQuery團(tuán)隊(duì)開發(fā)的JavaScript單元測(cè)試工具,功能強(qiáng)大且使用簡(jiǎn)單。目前所有的JQuery代碼都使用QUnit進(jìn)行測(cè)試,原生的JavaScript也可以使用QUnit。 |
1 .使用起來非常方便,有漂亮的外觀和完整的測(cè)試功能(包括異步測(cè)試);?? 2 .不需要依賴其它任何軟件包或框架,只要能運(yùn)行JS的地方就可以,QUnit本身只有一個(gè)JS文件和CSS文件,當(dāng)然如果需要可以和jQuery等其它框架集成; ??3 .不僅支持在瀏覽器中測(cè)試,還支持在Rhino和node.js等后端測(cè)試。 |
對(duì)自動(dòng)化支持不好,很難和Ant、Maven或自動(dòng)構(gòu)建等工具集成,主要用在瀏覽器中進(jìn)行測(cè)試。 異步困難 。語法不流暢 。不好配置
|
jasmine | Jasmine是一個(gè)有名的JavaScript單元測(cè)試框架,它是獨(dú)立的行為驅(qū)動(dòng)開發(fā)框架,語法清晰易懂。 |
1. 它是基于行為驅(qū)動(dòng)開發(fā)實(shí)現(xiàn)的測(cè)試框架,它的語法非常貼近自然語言 ,簡(jiǎn)單明了 ,容易理解 。2. 它有豐富的API,同時(shí)用戶也支持用戶擴(kuò)展 它的API,這一點(diǎn)很少有其它框架能夠做到。 |
在瀏覽器中的測(cè)試界面不如QUnit美觀、詳細(xì)。一異步測(cè)試麻煩
|
Mocha | Mocha是一個(gè)簡(jiǎn)單、靈活有趣的JavaScript 測(cè)試框架,用于Node.js和瀏覽器上的JavaScript應(yīng)用測(cè)試 |
1. 支持簡(jiǎn)單異步,包括 promise;2. 提供javascript API來運(yùn)行測(cè)試;3. non-ttys自動(dòng)檢測(cè)和禁用顏色;4. 支持異步測(cè)試超時(shí);5. 支持node debugger;6. 高擴(kuò)展性 |
部分領(lǐng)域缺少支持 |
斷言庫
- chai是一套TDD(測(cè)試驅(qū)動(dòng)開發(fā))/BDD(行為驅(qū)動(dòng)開發(fā))的斷言框架他包含有3個(gè)斷言庫,支持BDD風(fēng)格的expect/should和TDD風(fēng)格的assert,這里主要說明expect/should庫,BDD風(fēng)格說簡(jiǎn)單的就是你的測(cè)試代碼更加的語義化,讓你的斷言可讀性更好,expect/should庫都支持鏈?zhǔn)秸{(diào)用
(測(cè)試驅(qū)動(dòng)開發(fā)): 它要求在編寫某個(gè)功能的代碼之前先編寫測(cè)試代碼,然后只編寫使測(cè)試通過的功能代碼,通過測(cè)試來推動(dòng)整個(gè)開發(fā)的進(jìn)行。這有助于編寫簡(jiǎn)潔可用和高質(zhì)量的代碼,并加速開發(fā)過程。測(cè)試驅(qū)動(dòng)開發(fā)的基本過程如下:
1) 明確當(dāng)前要完成的功能。可以記錄成一個(gè) TODO 列表。
2) 快速完成針對(duì)此功能的測(cè)試用例編寫。
3) 測(cè)試代碼編譯不通過。
4) 編寫對(duì)應(yīng)的功能代碼。
5) 測(cè)試通過。
6) 對(duì)代碼進(jìn)行重構(gòu),并保證測(cè)試通過。
7) 循環(huán)完成所有功能的開發(fā)。
(行為驅(qū)動(dòng)開發(fā)): 使用通用語言,客戶和開發(fā)者可以一起定義出系統(tǒng)的行為,從而做出符合客戶需求的設(shè)計(jì)。但如果光有設(shè)計(jì),而沒有驗(yàn)證的手段,就無法檢驗(yàn)我們的實(shí)現(xiàn)是不是符合設(shè)計(jì)。所以 BDD還是要和測(cè)試結(jié)合在一起,用系統(tǒng)行為的定義來驗(yàn)證實(shí)現(xiàn)代碼。(有點(diǎn)厲害)
Mocha + Chai
import chai from 'chai';
const assert = chai.assert;
const expect = chai.expect; // 這個(gè)比較貼合自然語言
const should = chai.should();
// -----------
foo.should.be.a('string');
foo.should.equal('bar');
list.should.have.length(3);
obj.should.have.property('name');
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(list).to.have.length(3);
expect(obj).to.have.property('flavors');
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(list, 3);
assert.property(obj, 'flavors');
測(cè)試思路
基本思路:自身從函數(shù)的調(diào)用者出發(fā),對(duì)函數(shù)進(jìn)行各種情況的調(diào)用,查看其容錯(cuò)程度、返回結(jié)果是否符合預(yù)期。