寫這篇文章的初衷是,有個朋友來找我寫單元測試...自己懵逼了,開發了這麼久,還真沒有好好寫過測試用例,然後就和我朋友掰扯,我的論點是,前端開發沒必要寫單元測試,一般公司都有端對端測試就是點點點...開發人員一旦書寫測試用例,無形增加人員的開銷,或者是延緩迭代速率...他一句話,上頭需要...兩人啞口無言乘着空閒的時候就查閲了各種資料
思考一:是否有必要單元測試?(團隊是否擁有測試團隊的兩種情況)
思考二:實在要編寫單元測試如何編寫優雅的測試用例模塊?大家有空不妨告訴我你們公司會用到單元測試麼?有必要麼?
下面先自我歸總下
Mocha單元測試的基礎api使用和注意點
什麼是Mocha
字如其名,mocha(摩卡咖啡)其官網文檔也是如此一般
JavaScript 具有多種工具和框架,這些工具和框架可以在 Node.js 以及瀏覽器中進行測試驅動開發,例如:Jest, Jasmine, Mocha, QUnit, Karma, Cypress 等。
而mocha就是測試驅動開發中眾多工具之一
第一個測試套件
安裝 所需依賴
npm i mocha -g 全局安裝
npm i mocha --save-dev 項目依賴中安裝
終端中執行mocha將在當前目錄中需要test/文件夾並執行對應的測試用用例文件
下面我們建立個test目錄並新建index.test.js文件
// index.test.js
function add(v,v2){
return v + v2;
}
describe('# add()', function() {
it('should return 3 when the value 1 、 2', function() {
expect(add()).to.be.equal(3)
})
})
以上一個測試add()方法運算邏輯是否正常的功能測試用例就算是編寫好了
但是真實項目中,我們一般不會如此簡單的業務邏輯;一般都會包含測試異步代碼、測試同步代碼,以及一些騷操作提高效率
異步代碼測試
異步代碼在現代開發中很常見,mocha針對異步代碼也有對應的解決方案
1.使用回調方法
2.使用 Promise (用於支持 Promise 的環境)
3.使用async/await(用於支持異步 function 的環境)
異步接口請求的時候可以使用supertest提供http請求方案
1.使用回調方法
//異步接口請求測試
it('異步請求應該返回一個對象', function(done){
supertest
.get('https://api.github.com')
.end(function(err, res){
expect(res).to.be.an('object');
done();
});
});
如上使用 supertest方法能很好的進行異步回調處理
var expect = require('chai').expect;
function asyncFn (string, callback){
var withCallback \= typeof callback \=== 'function';
try {
let hash = string;
withCallback && callback(null, hash);
} catch (e) {
if (withCallback) {
callback(e);
} else {
throw e;
}
}
}
describe('# asyncFn()', function() {
it('異步函數回調處理', function(done) {
asyncFn('test',function(err,str){
// 使用 error 來調用 done() 回調函數,
// 來終止帶有錯誤的測試
if(err) return done(err);
expect(str).to.be.equal('test');
// 調用 done() 回調函數來終止測試 done();
})
})
})
如上使用回調函數處理異步代碼
2.使用 Promise
function promiseAsyncFn (str){
return new Promise((reolve,reject)=>{
asyncFn(string, (err, str) => {
return err ? reject(err) : resolve(str);
})
})
}
describe('# asyncFn()', function() {
it('異步函數回調處理 resolve', function(done) {
return promiseAsyncFn('test').then(function()=>{
expect(str).to.be.equal('test');
done()
})
})
it('異步函數回調處理 reject', function(done) {
return promiseAsyncFn('test').catch(
function(err)=>{
expect(function(){throw err}).to.throw(TypeError, 'The "data" argument must be one of type string, TypedArray, or DataView. Received type number');
done(err)
})
})
})
3.使用 async /await
describe('# asyncFn()', function() {
it('異步函數回調處理 resolve', async function(done) {
let data = await promiseAsyncFn('test')
expect(data).to.be.equal('test');
done()
})
it('異步函數回調處理 reject',async function(done) {
await promiseAsyncFn('test').catch(
function(err)=>{
expect(function(){throw err}).to.throw(TypeError, 'The "data" argument must be one of type string, TypedArray, or DataView. Received type number');
done(err)
})
})
})
看完上面三種異步代碼的處理方法熟悉js的小夥伴要開始扔磚了,和JavaScript好像一樣,我寫完之後發現確實一樣,但是我們不能大意其中done()的特性!
done()使用的主意事項
- it() 用例中必須調用done()結束測試,否則測試將一直運行直到超時。
- it() 中done()不能被調用多次,有且僅有一次
- done() 參數可以是錯誤,並可顯示指定錯誤
斷言庫chai
細心的同學應該發現了每個it用例彙總總會出現個expect,這是配合mocha處理錯誤判斷異常的斷言庫chai模塊中的一個方法,其他如Expect.js,Should.js;mocha中也擁有內置的assert模塊但是我們優先使用開發中更加常用的以及功能強大的chai
npm i --save-dev chai
expect風格
常用的expect斷言風格如下
// 相等或不相等
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斷言的寫法都是一樣的。頭部是expect方法,尾部是斷言方法,比如equal、a/an、ok、match等。兩者之間使用to或to.be連接。
Assert風格
var assert = require('chai').assert;
var numbers = [1, 2, 3, 4, 5];
assert.isArray(numbers, 'is array of numbers');
assert.include(numbers, 2, 'array contains 2');
assert.lengthOf(numbers, 5, 'array contains 5 numbers');
Should 風格
var should = require('chai').should();
var numbers = [1, 2, 3, 4, 5];
numbers.should.be.an('array').that.includes(2);
numbers.should.have.lengthOf(5);
mocha中的hooks
Mocha 提供了創建測試 hooks 的功能,hooks 基本上是配置為在測試之前或之後運行的邏輯。它們對於設置測試的前提條件或者在測試後清理資源很有用。使用默認的 BDD 接口,Mocha 提供了四個 hooks:
before()- 在塊中的第一個測試用例之前運行一次beforeEach()- 在每個測試用例前運行afterEach()- 在每個測試用例之後運行after()- 在塊中的最後一個測試用例之後運行一次
執行順序如下
before() -> beforeEach() -> test() -> afterEach() -> after()
創建一個簡單的hooks
describe('hooks', function() {
before(function() {
// 在本區塊的所有測試用例之前執行
});
after(function() {
// 在本區塊的所有測試用例之後執行
});
beforeEach(function() {
// 在本區塊的每個測試用例之前執行
});
afterEach(function() {
// 在本區塊的每個測試用例之後執行
});
// test cases
});
騷操作
mocha 同時具備一些自身可配參數,比如修改用例超時時間、遞歸執行test文件夾下的所有路徑、使用skip()、only()控制it、describe是否執行...
1. 包含/排除測試skip()、only()
// 此測試套件中的測試將運行
describe.only('#flattenArray()', function() {
describe('#二級-1()', function() {
it.only('should merge two arrays', function() {})
it('should merge two arrays123123', function() {})
})
// it('should flatten array', function() {})
describe.skip('#二級-2()', function() {
it('should merge two arrays', function() {})
})
})
// 此測試套件中的測試無法運行
describe('#mergeArray()', function() {
it('should merge two arrays', function() {})
})
結果如下:
only會影響測試套件、測試用例
如上在第一個describe使用了only
之後相鄰的測試用例則被規避,不執行了
2. 重複測試
mocha 提供了 this.retries () 函數,該函數可以讓您指定允許重試的次數。對於每次重試,Mocha 都會運行重新 beforeEach () 和 afterEach () hooks,但不會重新運行 before () 和 after () 鈎子。
// this.retries() 函數的用法
describe('test medium site', function() {
// 這個套件中所有失敗的測試都會重試2次
this.retries(2)
it('should load medium homepage', function() {
// 這個測試用例測試失敗後,會被重複5次
this.retries(5)
cy.visit('https://medium.com')
})
})
3. 慢速定義
this.slow(value) 設定一個值界定當前測試用例超過這個臨界點是慢的
describe('slow test', function() {
// 在一秒之後,測試將被認為是緩慢的
this.slow(1000)
// 在指定的一秒鐘之後完成
it('should be complete in a second', function(done) {
setTimeout(done, 1500)
})
// 立即完成
it('should be complete instantly', function() {})
})
4. 超時定義
this.timeout(value) 可設置一個數值告訴mocha最大等待時間,超出時間則超時,mocha默認超時時間是2S
describe('some time-consuming operation', function() {
// 給這個測試用例設置 5 秒的超時時間
this.timeout(5000)
before('some long step', function(done) {
// 設置 hooks 的超時時間
this.timeout(2500)
setTimeout(done, 2250)
})
it('should take less than 200ms', function(done) {
// 為當前測試用例設置超時時間
this.timeout(200)
setTimeout(done, 150)
})
})
MOCHA CLI
mocha --watch
// 標誌指示 Mocha 監聽測試文件的變動並重新運行測試。這對於在開發過程中編寫測試非常有用。
mocha --async-only
// 標誌強制所有測試必須傳遞迴調函數或返回一個 promise,從而異步執行。對於未指定回調函數或者未返回 promise 的測試將被標記為失敗
mocha --bail
// 標誌指定只要有一個測試用例沒有通過,就停止後面所有的測試用例。這對於持續繼承很有用
mocha -t 3000
// 等價於`this.timeout()`方法,這個作用於全局
mocha --no-timeouts
// 等價於`--timeout 0``this.timeout(0)`,表示~~~~禁用超時限制
mocha --slow 100
// 默認的閾值是 75ms,標記速率的閾值
mocha --require should
// 標誌允許您導入在測試文件中使用的模塊 / 庫(例如斷言庫),而不是在代碼中手動調用 require ()。
mocha -R list
// 可以指定測試報告的格式。默認的是`spec`格式。Mocha 還允許您使用此標誌指定第三方報告格式。
mocha 還能使用將測試報告跑在瀏覽器中,結合一些插件能提高一定效率;
説到這裏,我認為一個成熟的開發團隊,應該是有必要也是必須有意識的書寫測試用例,為後人以及新需求搭建穩固的基礎,啦啦啦
歡迎來到自己的blog小站留言