一個 bug 被隱藏的時間越長,修復這個 bug 的代價就越大。大量的研究數據指出:最後才修改一個 bug 的代價是在 bug 產生時修改它的代價的10倍。因此要防患於未然。html
JavaScript
做爲 web
端使用最普遍的編程語言,它是動態語言,缺少靜態類型檢查,因此在代碼編譯期間,很難發現像變量名寫錯
,調用不存在的方法, 賦值或傳值的類型錯誤
等錯誤。前端
例以下面的例子, 這種類型不符的狀況在代碼中很是容易發生 function foo(x) { return x + 10 } foo('Hello!') //'Hello!10'
在JavaScript語言中,除了做爲數字的加運算外,也能夠看成字符串的鏈接運算符。這固然不是咱們想要的結果。
當開發完一個功能模塊的時候,如何肯定你的模塊有沒有 bug 呢?一般的作法是根據具體的業務,執行 debug 模式,一點一點深刻到代碼中去查看。若是你一直都是這樣,那麼你早就已經 OUT 了。如今更先進的作法是自動化測試, 寫好測試用例, 執行一個指令,就可快速知道代碼有沒有缺陷,以及出錯的地方。vue
在平常的開發中,代碼的完工其實並不等於開發的完工。若是沒有單元測試,不能保證代碼可以正常運行。node
測試不可能保證一個程序是徹底正確的,可是測試卻能夠加強程序員對程序健壯性,穩定性的信心,測試可讓咱們相信程序作了咱們指望它作的事情。測試可以使咱們儘早的發現程序的 bug 和不足。作完開發後,用測試框架轟擊系統,可以經受住測試框架挑戰過的代碼,纔是健壯的代碼。 單元測試能加強開發人員對代碼的信心。git
測試人員作的只是業務上的集成測試,也就是黑盒測試,測試出的 bug 的範圍相對而言比較廣,很難精確到單個方法, 不可以精準地定位問題。程序員
JavaScript代碼測試有不少分類,好比單元測試(unit test)、集成測試(integration test)、功能測試(functional test)、端到端測試(end to end test)、迴歸測試(regression test)、瀏覽器測試(browser test)…github
單元測試指的是測試小的代碼塊,一般指的是獨立測試單個函數。若是某個測試依賴於一些外部資源,好比網絡或者數據庫,那它就不是單元測試。單元測試是從程序員的角度編寫的,保證一些方法執行特定的任務,給出特定輸入,獲得預期的結果。web
單元測試通常很容易寫。一個單元測試一般是這樣的:爲某個函數提供某些輸入值,而後驗證函數的返回值是否正確。然而,若是你的代碼設計很是糟糕,則單元測試會很難寫。從另外一個角度理解,單元測試能夠幫助咱們寫更好的代碼。單元測試能夠幫助咱們避免一些常見的BUG。一般,程序員會在同一個細節上反覆犯錯,若是爲這些Bug添加單元測試,則能夠有效避免這種狀況。固然,你也可使用集成測試和功能測試來解決這個問題,可是單元測試更加適合,由於單元測試更加細緻,能夠幫助咱們快速定位和解決問題。數據庫
集成測試就是測試應用中不一樣模塊如何集成,如何一塊兒工做,這和它的名字一致。集成測試與單元測試類似,可是它們也有很大的不一樣:單元測試是測試每一個獨立的模塊,而集成測試剛好相反。好比,當測試須要訪問數據庫的代碼時,單元測試不會真的去訪問數據庫,而集成測試則會。編程
單元測試不夠時,這時就須要集成測試了。當你須要去驗證兩個獨立的模塊,好比數據庫和應用,保證它們可以正確的一塊兒工做,這時就須要集成測試了。爲了驗證測試結果,你就須要經過查詢數據庫驗證數據正確性。
集成測試一般比單元測試慢,由於它更加複雜。而且,集成測試還須要配置測試環境,好比配置測試數據庫或者其餘依賴的組件。這就使得編寫和維護集成測試更加困難,所以,你應該專一於單元測試,除非你真的須要集成測試。
你須要的集成測試應該少於單元測試。除非你須要測試多個模塊,或者你的代碼太複雜時,你才須要集成測試。而且,當你的代碼過於複雜時,建議優化代碼以便進行單元測試,而不是直接寫集成測試。
一般,咱們可使用單元測試工具編寫集成測試。
功能測試有時候也被稱做端到端測試,或者瀏覽器測試,它們指的是同一件事。功能測試是從用戶的角度編寫的,測試確保用戶執行它所指望的工做。
功能測試指的是測試應用的某個完整的功能,它從一個用戶的角度出發,認爲整個系統都是一個黑箱,只有UI會暴露給用戶。對於網頁應用,功能測試意味着使用工具模擬瀏覽器,而後經過點擊頁面來測試應用。
單元測試能夠測試單個函數,集成測試能夠測試兩個模塊一塊兒工做。功能測試則徹底是另一個層次。你能夠有上百個單元測試,可是一般你只有少許的功能測試。這是由於功能測試太複雜了,難於編寫和維護。功能測試很慢,由於它須要模擬真實用戶進行網頁交互。
事實上,你不須要編寫很是詳細的功能測試。功能測試並不意味着你須要測試每個功能,其實,你只須要測試一些常見的用戶行爲。若是你須要在瀏覽器中手動測試應用的某個流程,好比註冊帳號,這時你能夠編寫一個功能測試。
對於單元測試,你會使用代碼去驗證結果,在功能測試中也應該這樣作。以註冊帳號爲例,你能夠驗證瀏覽器是否跳轉到了」感謝註冊」頁面。
當有些測試你須要手動在瀏覽器下重複進行時,你應該編寫功能測試。注意不要寫得太細緻了,不然維護這些測試將是一個噩夢。
測試JavaScript代碼時,應該着重於單元測試,它很是容易編寫和維護,除了能夠減小BUG還有不少益處。而集成測試與功能測試應該做爲補充。
代碼有測試用例,雖不能說百分百無bug,但至少說明測試用例覆蓋到的場景是沒有問題的。有測試用例,發佈前跑一下,能夠杜絕各類疏忽而引發的功能bug。若是能經過單元測試,那麼經過後續測試且軟件總體正常運行的機率大大提升
自動化測試另一個重要特色就是快速反饋,反饋越迅速意味着開發效率越高。拿UI組件爲例,開發過程都是打開瀏覽器刷新頁面點點點才能肯定UI組件工做狀況是否符合本身預期。接入自動化測試之後,經過腳本代替這些手動點擊,接入代碼watch後每次保存文件都能快速得知本身的的改動是否影響功能,節省了不少時間,畢竟機器幹事情比人老是要快得多。若是程序有bug,咱們運行一次所有單元測試,找到不經過的測試,能夠很快地定位對應的執行代碼。單元測試發現的問題定位到細節,容易修改,節省時間。修復代碼後,運行對應的單元測試;如還不經過,繼續修改,運行測試.....直到測試經過。
重構後把代碼改壞了,對總體系統構成破壞的狀況並很多見。因爲大多數狀況下,全部模塊或業務功能不是孤立的,可謂牽一髮動全身,你改一個方法可能致使整個項目運行不起來
若是你有單元測試,狀況大不相同。寫完一個類,把單元測試寫了,確保這個類邏輯正確;每一個類保證邏輯正確,拼在一塊兒確定不出問題。能夠放心一邊重構,一邊運行項目;而不是總體重構完,提心跳膽地run。
測試主要是測試框架、斷言庫, 代碼覆蓋率工具,仿真工具 , 測試驅動(測試任務管理工具)組成:
測試框架: 如何組織測試,主要由Mocha、Jasmine,Jest ,AVA, Tape等,測試主要提供了清晰簡明的語法來描述測試用例,以及對測試用例分組,測試框架會抓取到代碼拋出的AssertionError,並增長一大堆附加信息,好比那個用例掛了,爲何掛等等。測試框架一般提供TDD(測試驅動開發)或BDD(行爲驅動開發)的測試語法來編寫測試用例。不一樣的測試框架支持不一樣的測試語法,好比Mocha既支持TDD也支持BDD,而Jasmine只支持BDD。當前流行 BDD 的測試結構。
斷言庫:Should.js、chai、expect.js等等,斷言庫提供了不少語義化的方法來對值作各類各樣的判斷。固然也能夠不用斷言庫,Node.js中也能夠直接使用原生assert庫。
代碼覆蓋率:istanbul等爲代碼在語法級分支上打點,運行了打點後的代碼,根據運行結束後收集到的信息和打點時的信息來統計出當前測試用例對源碼的覆蓋狀況。
測試驅動(測試任務管理工具)
karma: 是一個基於 Node.js 的 JavaScript 測試執行過程管理工具(Test Runner)。設置測試須要的框架、環境、源文件、測試文件等,配置完後,就能夠輕鬆地執行測試,該工具可用於測試全部主流 Web 瀏覽器,
這個測試工具的一個強大特性就是,它能夠監控 (Watch) 文件的變化,而後自行執行,經過 console.log 顯示測試結果。
buster.js: 另一個工具,不過目前處於deta版本,不只能夠在瀏覽器端,還能夠在node端
單元測試應該:簡單,快速執行,有清晰的錯誤報告。
注:測試驅動型和行爲驅動型的區別
TDD:站在程序員的角度,寫測試代碼。測試驅動型的開發方式,先寫測試代碼,以後編寫能經過測試的業務代碼,能夠不斷的在能經過測試的狀況下重構 。
BDD:站在用戶的角度,寫測試代碼。 是測試驅動開發的進化,測試代碼的風格是預期結果,更關注功能和設計,看起來像需求文檔。定義系統的行爲是主要工做,而對系統行爲的描述則變成了測試標準
其實都是先寫測試代碼,感受BDD 風格更人性。
總結一下,Mocha ,Jasmine用的人最多,社區最成熟,靈活,可配置性強易拓展,Jest 開箱即用,裏邊啥都有提供全面的方案,Tape 最精簡。
Mocha 跟 Jasmine 是目前最火的兩個單元測試框架,基本上目前前端單元測試就在這兩個庫之間選了。總的來講就是Jasmine功能齊全,配置方便,Mocha靈活自由,自由配置。 二者功能覆蓋範圍粗略能夠表示爲:
Jasmine(2.x) === Mocha + Chai + Sinon - mockserver
實際使用後以爲jasmine因爲各類功能內建,斷言方式或者異步等風格相對比較固定,沒有自帶mockserver, 須要這功能的得另外配置, Cha i和 Sinon(賽蘭)畢竟是專門作特定功能的框架,用 Mocha + Chai + Sinon 這種方式會想對舒爽一點。
Assert
var assert = require('chai').assert , foo = 'bar' , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; assert.typeOf(foo, 'string'); // without optional message assert.typeOf(foo, 'string', 'foo is a string'); // with optional message assert.equal(foo, 'bar', 'foo equal `bar`'); assert.lengthOf(foo, 3, 'foo`s value has a length of 3'); assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
BBD風格的斷言庫
expect
var expect = require('chai').expect , foo = 'bar' , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; expect(foo).to.be.a('string'); expect(foo).to.equal('bar'); expect(foo).to.have.lengthOf(3); expect(beverages).to.have.property('tea').with.lengthOf(3);
should
var should = require('chai').should() //actually call the function , foo = 'bar' , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; foo.should.be.a('string'); foo.should.equal('bar'); foo.should.have.lengthOf(3); beverages.should.have.property('tea').with.lengthOf(3);
建議使用expect
,should不兼容IE
// equal 相等或不相等 expect(4 + 5).to.be.equal(9); expect(4 + 5).to.be.not.equal(10); expect('hello').to.equal('hello'); expect(42).to.equal(42); expect(1).to.not.equal(true); expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' }); expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' }); // above 斷言目標的值大於某個value,若是前面有length的鏈式標記,則能夠用來判斷數組長度或者字符串長度 expect(10).to.be.above(5); expect('foo').to.have.length.above(2); expect([ 1, 2, 3 ]).to.have.length.above(2); 相似的還有least(value)表示大於等於;below(value)表示小於;most(value)表示小於等於 // 判斷目標是否爲布爾值true(隱式轉換) expect('everthing').to.be.ok; expect(1).to.be.ok; expect(false).to.not.be.ok; expect(undefined).to.not.be.ok; expect(null).to.not.be.ok; // true/false 斷言目標是否爲true或false expect(true).to.be.true; expect(1).to.not.be.true; expect(false).to.be.false; expect(0).to.not.be.false; // null/undefined 斷言目標是否爲null/undefined expect(null).to.be.null; expect(undefined).not.to.be.null; expect(undefined).to.be.undefined; expect(null).to.not.be.undefined; // NaN 斷言目標值不是數值 expect('foo').to.be.NaN; expect(4).not.to.be.NaN; // 判斷類型大法(能夠實現上面的一些例子):a/an expect('test').to.be.a('string'); expect({ foo: 'bar' }).to.be.an('object'); expect(foo).to.be.an.instanceof(Foo); expect(null).to.be.a('null'); expect(undefined).to.be.an('undefined'); expect(new Error).to.be.an('error'); expect(new Promise).to.be.a('promise'); // 包含關係:用來斷言字符串包含和數組包含。若是用在鏈式調用中,能夠用來測試對象是否包含某key 能夠混着用。 expect([1,2,3]).to.include(2); expect('foobar').to.contain('foo'); expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo'); // 判斷空值 expect([]).to.be.empty; expect('').to.be.empty; expect({}).to.be.empty; // match expect('foobar').to.match(/^foo/); // exist 斷言目標既不是null也不是undefined var foo = 'hi' , bar = null, baz; expect(foo).to.exist; expect(bar).to.not.exist; expect(baz).to.not.exist; // within斷言目標值在某個區間範圍內,能夠與length連用 expect(7).to.be.within(5,10); expect('foo').to.have.length.within(2,4); expect([ 1, 2, 3 ]).to.have.length.within(2,4); // instanceOf 斷言目標是某個構造器產生的事例 var Tea = function (name) { this.name = name; } , Chai = new Tea('chai'); expect(Chai).to.be.an.instanceof(Tea); expect([ 1, 2, 3 ]).to.be.instanceof(Array); // property(name, [value]) 斷言目標有以name爲key的屬性,而且能夠指定value斷言屬性值是嚴格相等的,此[value]參數爲可選,若是使用deep鏈式調用,能夠在name中指定對象或數組的引用表示方法 // simple referencing var obj = { foo: 'bar' }; expect(obj).to.have.property('foo'); expect(obj).to.have.property('foo', 'bar');// 相似於expect(obj).to.contains.keys('foo') // deep referencing var deepObj = { green: { tea: 'matcha' }, teas: [ 'chai', 'matcha', { tea: 'konacha' } ] }; expect(deepObj).to.have.deep.property('green.tea', 'matcha'); expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); // ownproperty 斷言目標擁有本身的屬性,非原型鏈繼承 expect('test').to.have.ownProperty('length'); // throw 斷言目標拋出特定的異常 var err = new ReferenceError('This is a bad function.'); var fn = function () { throw err; } expect(fn).to.throw(ReferenceError); expect(fn).to.throw(Error); expect(fn).to.throw(/bad function/); expect(fn).to.not.throw('good function'); expect(fn).to.throw(ReferenceError, /bad function/); expect(fn).to.throw(err); expect(fn).to.not.throw(new RangeError('Out of range.')); // satisfy(method) 斷言目標經過一個真值測試 expect(1).to.satisfy(function(num) { return num > 0; })
可執行語句的每一行是否都被執行了,不包括註釋,空白行 行覆蓋經常被人指責爲「最弱的覆蓋」,爲何這麼說呢,舉一個例子
function foo(a, b) { return a / b; }
TeseCase: a = 10, b = 5
測試人員的測試結果會告訴你,他的代碼覆蓋率達到了100%,而且全部測試案例都經過了。咱們的語句覆蓋率達到了所謂的100%,可是卻沒有發現最簡單的Bug,好比,當我讓b=0時,會拋出一個除零異常。
4個指標當中,行覆蓋率和語句覆蓋率很相近;在代碼規範的狀況下,規範要求一行寫一個語句 它們應該是同樣的
4個指標當中,分支覆蓋率是最重要的,它包括: !
, &&
, ||
, ?: ;
if
和 else-if else
switch - case
等等各類包含分支的狀況
近幾年前端工程化的發展風起雲涌,可是前端自動化測試這塊內容你們卻彷佛不過重視。雖然項目迭代過程當中會有專門的測試人員進行測試,但等他們來進行測試時,代碼已經開發完成的狀態。與之相比,若是咱們在開發過程當中就進行了測試會有以下的好處:
固然,凡事都有兩面性,好處雖然明顯,卻並非全部的項目都值得引入測試框架,畢竟維護測試用例也是須要成本的。對於一些需求頻繁變動、複用性較低的內容,好比活動頁面,讓開發專門抽出人力來寫測試用例確實得不償失。
而適合引入測試場景大概有這麼幾個:
參考連接
1.https://www.jianshu.com/p/f200a75a15d2 Chai.js斷言庫API中文文檔
2.http://www.ruanyifeng.com/blog/2015/06/istanbul.html 代碼覆蓋率工具 Istanbul 入門教程
3.https://segmentfault.com/a/1190000012654035 Vue單元測試實戰教程(Mocha/Karma + Vue-Test-Utils + Chai)
4.http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html 測試框架 Mocha 實例教程
5.https://vue-test-utils.vuejs.org/zh/guides/#%E8%B5%B7%E6%AD%A5 Vue Test Utils教程
6.https://www.jianshu.com/p/c7c86b8f376c mocha 的基本介紹&&expect風格斷言庫的基本語法