當前位置:
首頁 > 科技 > 聊一聊前端自動化測試(下)

聊一聊前端自動化測試(下)

頁面測試

對於瀏覽器里跑的前端代碼,做測試要比Node.js模塊要麻煩得多。Node.js模塊純js代碼,使用V8運行在本地,測試用的各種各樣的依賴和工具都能快速的安裝,而前端代碼不僅僅要測試js,CSS等等,更麻煩的事需要模擬各種各樣的瀏覽器,比較常見的前端代碼測試方案有下面幾種:

構建一個測試頁面,人肉直接到虛擬機上開各種瀏覽器跑測試頁面(比如公司的f2etest)。這個方案的缺點就是不好做代碼覆蓋率測試,也不好持續化集成,同時人肉工作較多

使用PhantomJS構建一個偽造的瀏覽器環境跑單元測試,好處是解決了代碼覆蓋率問題,也可以做持續集成。這個方案的缺點是PhantomJS畢竟是Qt的webkit,並不是真實瀏覽器環境,PhantomJS也有各種各樣兼容性坑

通過Karma調用本機各種瀏覽器進行測試,好處是可以跨瀏覽器做測試,也可以測試覆蓋率,但持續集成時需要注意只能開PhantomJS做測試,畢竟集成的Linux環境不可能有瀏覽器。這可以說是目前看到的最好的前端代碼測試方式了

這裡以gulp為構建工具做測試,後面在React組件測試部分再介紹以webpack為構建工具做測試

叒一個煎蛋的栗子

前端代碼依舊是js,一樣可以用Mocha+Should.js來做單元測試。打開node_modules下的Mocha和Should.js,你會發現這些優秀的開源工具已經非常貼心的提供了可在瀏覽器中直接運行的版本:mocha/mocha.js和should/should.min.js,只需要把他們通過script標籤引入即可,另外Mocha還需要引入自己的樣式mocha/mocha.css

首先看一下我們的前端項目結構:

.

├── gulpfile.js

├──package.json

├── src

│ └── index.js

└── test

├── test.html

└── test.js

比如這裡源碼src/index.js就是定義一個全局函數:

window.render=function(){

varctn=document.createElement( div );

ctn.setAttribute( id , tmall );

ctn.appendChild(document.createTextNode( 天貓前端招人,有意向的請發送簡歷至lingyucoder@gmail.com ));

document.body.appendChild(ctn);

}

而測試頁面test/test.html大致上是這個樣子:

head里引入了測試框架Mocha和斷言庫Should.js,測試的結果會被顯示在

這個容器里,而test/test.js里則是我們的測試的代碼。

前端頁面上測試和Node.js上測試沒啥太大不同,只是需要指定Mocha使用的UI,並需要手動調用mocha.run():

在瀏覽器里打開test/test.html頁面,就可以看到效果了:

在不同的瀏覽器里打開這個頁面,就可以看到當前瀏覽器的測試了。這種方式能兼容最多的瀏覽器,當然要跨機器之前記得把資源上傳到一個測試機器都能訪問到的地方,比如CDN。

測試頁面有了,那麼來試試接入PhantomJS吧

使用PhantomJS進行測試

PhantomJS是一個模擬的瀏覽器,它能執行js,甚至還有webkit渲染引擎,只是沒有瀏覽器的界面上渲染結果罷了。我們可以使用它做很多事情,比如對網頁進行截圖,寫爬蟲爬取非同步渲染的頁面,以及接下來要介紹的——對頁面做測試。

當然,這裡我們不是直接使用PhantomJS,而是使用mocha-phantomjs來做測試。npm install --save-dev mocha-phantomjs安裝完成後,就可以運行命令./node_modules/.bin/mocha-phantomjs ./test/test.html來對上面那個test/test.html的測試了:

單元測試沒問題了,接下來就是代碼覆蓋率測試

覆蓋率打點

首先第一步,改寫我們的gulpfile.js:

use strict ;

constgulp=require( gulp );

constistanbul=require( gulp-istanbul );

gulp.task( test ,function(){

returngulp.src([ src/**/*.js ])

.pipe(istanbul({

coverageVariable: __coverage__

}))

.pipe(gulp.dest( build-test ));

});

這裡把覆蓋率結果保存到__coverage__裡面,把打完點的代碼放到build-test目錄下,比如剛才的src/index.js的代碼,在運行gulp test後,會生成build-test/index.js,內容大致是這個樣子:

var__cov_WzFiasMcIh_mBvAjOuQiQg=(Function( return this ))();

if(!__cov_WzFiasMcIh_mBvAjOuQiQg.__coverage__){__cov_WzFiasMcIh_mBvAjOuQiQg.__coverage__={};}

__cov_WzFiasMcIh_mBvAjOuQiQg=__cov_WzFiasMcIh_mBvAjOuQiQg.__coverage__;

if(!(__cov_WzFiasMcIh_mBvAjOuQiQg[ /Users/lingyu/gitlab/dev/mui/test-page/src/index.js ])){

__cov_WzFiasMcIh_mBvAjOuQiQg[ /Users/lingyu/gitlab/dev/mui/test-page/src/index.js ]={"path":"/Users/lingyu/gitlab/dev/mui/test-page/src/index.js","s":{"1":,"2":,"3":,"4":,"5":},"b":{},"f":{"1":},"fnMap":{"1":{"name":"(anonymous_1)","line":1,"loc":{"start":{"line":1,"column":16},"end":{"line":1,"column":27}}}},"statementMap":{"1":{"start":{"line":1,"column":},"end":{"line":6,"column":1}},"2":{"start":{"line":2,"column":2},"end":{"line":2,"column":42}},"3":{"start":{"line":3,"column":2},"end":{"line":3,"column":34}},"4":{"start":{"line":4,"column":2},"end":{"line":4,"column":85}},"5":{"start":{"line":5,"column":2},"end":{"line":5,"column":33}}},"branchMap":{}};

}

__cov_WzFiasMcIh_mBvAjOuQiQg=__cov_WzFiasMcIh_mBvAjOuQiQg[ /Users/lingyu/gitlab/dev/mui/test-page/src/index.js ];

__cov_WzFiasMcIh_mBvAjOuQiQg.s[ 1 ]++;window.render=function(){__cov_WzFiasMcIh_mBvAjOuQiQg.f[ 1 ]++;__cov_WzFiasMcIh_mBvAjOuQiQg.s[ 2 ]++;varctn=document.createElement( div );__cov_WzFiasMcIh_mBvAjOuQiQg.s[ 3 ]++;ctn.setAttribute( id , tmall );__cov_WzFiasMcIh_mBvAjOuQiQg.s[ 4 ]++;ctn.appendChild(document.createTextNode( 天貓前端招人uFF0C有意向的請發送簡歷至lingyucoder@gmail.com ));__cov_WzFiasMcIh_mBvAjOuQiQg.s[ 5 ]++;document.body.appendChild(ctn);};

這都什麼鬼!不管了,反正運行它就好。把test/test.html裡面引入的代碼從src/index.js修改為build-test/index.js,保證頁面運行時使用的是編譯後的代碼。

編寫鉤子

運行數據會存放到變數__coverage__里,但是我們還需要一段鉤子代碼在單元測試結束後獲取這個變數里的內容。把鉤子代碼放在test/hook.js下,裡面內容這樣寫:

use strict ;

varfs=require( fs );

module.exports={

afterEnd:function(runner){

varcoverage=runner.page.evaluate(function(){

returnwindow.__coverage__;

});

if(coverage){

console.log( Writing coverage to coverage/coverage.json );

fs.write( coverage/coverage.json ,JSON.stringify(coverage), w );

}else{

console.log( No coverage data generated );

}

}

};

這樣準備工作工作就大功告成了,執行命令./node_modules/.bin/mocha-phantomjs ./test/test.html --hooks ./test/hook.js,可以看到如下圖結果,同時覆蓋率結果被寫入到coverage/coverage.json 裡面了。

生成頁面

有了結果覆蓋率結果就可以生成覆蓋率頁面了,首先看看覆蓋率概況吧。執行命令./node_modules/.bin/istanbul report --root coverage text-summary,可以看到下圖:

還是原來的配方,還是想熟悉的味道。接下來運行./node_modules/.bin/istanbul report --root coverage lcov生成覆蓋率頁面,執行完後open coverage/lcov-report/index.html,點擊進入到src/index.js:

一顆賽艇!這樣我們對前端代碼就能做覆蓋率測試了

接入Karma

Karma是一個測試集成框架,可以方便地以插件的形式集成測試框架、測試環境、覆蓋率工具等等。Karma已經有了一套相當完善的插件體系,這裡嘗試在PhantomJS、Chrome、FireFox下做測試,首先需要使用npm安裝一些依賴:

karma:框架本體

karma-mocha:Mocha測試框架

karma-coverage:覆蓋率測試

karma-spec-reporter:測試結果輸出

karma-phantomjs-launcher:PhantomJS環境

phantomjs-prebuilt: PhantomJS最新版本

karma-chrome-launcher:Chrome環境

karma-firefox-launcher:Firefox環境

安裝完成後,就可以開啟我們的Karma之旅了。還是之前的那個項目,我們把該清除的清除,只留下源文件和而是文件,並增加一個karma.conf.js 文件:

.

├── karma.conf.js

├──package.json

├── src

│ └── index.js

└── test

└── test.js

karma.conf.js是Karma框架的配置文件,在這個例子里,它大概是這個樣子:

use strict ;

module.exports=function(config){

config.set({

frameworks:[ mocha ],

files:[

./node_modules/should/should.js ,

src/**/*.js ,

test/**/*.js

],

preprocessors:{

src/**/*.js :[ coverage ]

},

plugins:[ karma-mocha , karma-phantomjs-launcher , karma-chrome-launcher , karma-firefox-launcher , karma-coverage , karma-spec-reporter ],

browsers:[ PhantomJS , Firefox , Chrome ],

reporters:[ spec , coverage ],

coverageReporter:{

dir: coverage ,

reporters:[{

type: json ,

subdir: . ,

file: coverage.json ,

},{

type: lcov ,

subdir: .

},{

type: text-summary

}]

}

});

};

這些配置都是什麼意思呢?這裡挨個說明一下:

frameworks: 使用的測試框架,這裡依舊是我們熟悉又親切的Mocha

files:測試頁面需要載入的資源,上面的test目錄下已經沒有test.html了,所有需要載入內容都在這裡指定,如果是CDN上的資源,直接寫URL也可以,不過建議儘可能使用本地資源,這樣測試更快而且即使沒網也可以測試。這個例子里,第一行載入的是斷言庫Should.js,第二行是src下的所有代碼,第三行載入測試代碼

preprocessors:配置預處理器,在上面files載入對應的文件前,如果在這裡配置了預處理器,會先對文件做處理,然後載入處理結果。這個例子里,需要對src目錄下的所有資源添加覆蓋率打點(這一步之前是通過gulp-istanbul來做,現在karma-coverage框架可以很方便的處理,也不需要鉤子啥的了)。後面做React組件測試時也會在這裡使用webpack

plugins:安裝的插件列表

browsers:需要測試的瀏覽器,這裡我們選擇了PhantomJS、FireFox、Chrome

reporters:需要生成哪些代碼報告

coverageReporter:覆蓋率報告要如何生成,這裡我們期望生成和之前一樣的報告,包括覆蓋率頁面、lcov.info、coverage.json、以及命令行里的提示

好了,配置完成,來試試吧,運行./node_modules/karma/bin/karma start --single-run,可以看到如下輸出:

可以看到,Karma首先會在9876埠開啟一個本地服務,然後分別啟動PhantomJS、FireFox、Chrome去載入這個頁面,收集到測試結果信息之後分別輸出,這樣跨瀏覽器測試就解決啦。如果要新增瀏覽器就安裝對應的瀏覽器插件,然後在browsers里指定一下即可,非常靈活方便。

那如果我的mac電腦上沒有IE,又想測IE,怎麼辦呢?可以直接運行./node_modules/karma/bin/karma start啟動本地伺服器,然後使用其他機器開對應瀏覽器直接訪問本機的9876埠(當然這個埠是可配置的)即可,同樣移動端的測試也可以採用這個方法。這個方案兼顧了前兩個方案的優點,彌補了其不足,是目前看到最優秀的前端代碼測試方案了

React組件測試

去年React旋風一般席捲全球,當然天貓也在技術上緊跟時代腳步。天貓商家端業務已經全面切入React,形成了React組件體系,幾乎所有新業務都採用React開發,而老業務也在不斷向React遷移。React大紅大紫,這裡單獨拉出來講一講React+webpack的打包方案如何進行測試

這裡只聊React Web,不聊React Native

事實上天貓目前並未採用webpack打包,而是Gulp+Babel編譯React CommonJS代碼成AMD模塊使用,這是為了能夠在新老業務使用上更加靈活,當然也有部分業務採用webpack打包並上線

叕一個煎蛋的栗子

這裡創建一個React組件,目錄結構大致這樣(這裡略過CSS相關部分,只要跑通了,集成CSS像PostCSS、Less都沒啥問題):

.

├── demo

├── karma.conf.js

├──package.json

├── src

│ └── index.jsx

├── test

│ └── index_spec.jsx

├── webpack.dev.js

└── webpack.pub.js

React組件源碼src/index.jsx大概是這個樣子:

importReactfrom react ;

classWelcomeextendsReact.Component{

constructor(){

super();

}

render(){

return{this.props.content};

}

}

Welcome.displayName= Welcome ;

Welcome.propTypes={

/**

* content of element

*/

content:React.PropTypes.string

};

Welcome.defaultProps={

content: Hello Tmall

};

module.exports=Welcome;

那麼對應的test/index_spec.jsx則大概是這個樣子:

import should ;

importWelcomefrom ../src/index.jsx ;

importReactDOMfrom react-dom ;

importReactfrom react ;

importTestUtilsfrom react-addons-test-utils ;

describe( test ,function(){

constcontainer=document.createElement( div );

document.body.appendChild(container);

afterEach(()=>{

ReactDOM.unmountComponentAtNode(container);

});

it( Hello Tmall ,function(){

letcp=ReactDOM.render(,container);

letwelcome=TestUtils.findRenderedComponentWithType(cp,Welcome);

ReactDOM.findDOMNode(welcome).textContent.should.be.eql( Hello Tmall );

});

});

由於是測試React,自然要使用React的TestUtils,這個工具庫提供了不少方便查找節點和組件的方法,最重要的是它提供了模擬事件的API,這可以說是UI測試最重要的一個功能。更多關於TestUtils的使用請參考React官網,這裡就不扯了...

代碼有了,測試用例也有了,接下就差跑起來了。karma.conf.js肯定就和上面不一樣了,首先它要多一個插件karma-webpack,因為我們的React組件是需要webpack打包的,不打包的代碼壓根就沒法運行。另外還需要注意代碼覆蓋率測試也出現了變化。因為現在多了一層Babel編譯,Babel編譯ES6、ES7源碼生成ES5代碼後會產生很多polyfill代碼,因此如果對build完成之後的代碼做覆蓋率測試會包含這些polyfill代碼,這樣測出來的覆蓋率顯然是不可靠的,這個問題可以通過isparta-loader來解決。React組件的karma.conf.js大概是這個樣子:

use strict ;

constpath=require( path );

module.exports=function(config){

config.set({

frameworks:[ mocha ],

files:[

./node_modules/phantomjs-polyfill/bind-polyfill.js ,

test/**/*_spec.jsx

],

plugins:[ karma-webpack , karma-mocha ,, karma-chrome-launcher , karma-firefox-launcher , karma-phantomjs-launcher , karma-coverage , karma-spec-reporter ],

browsers:[ PhantomJS , Firefox , Chrome ],

preprocessors:{

test/**/*_spec.jsx :[ webpack ]

},

reporters:[ spec , coverage ],

coverageReporter:{

dir: coverage ,

reporters:[{

type: json ,

subdir: . ,

file: coverage.json ,

},{

type: lcov ,

subdir: .

},{

type: text-summary

}]

},

webpack:{

module:{

loaders:[{

test:/.jsx?/,

loaders:[ babel ]

}],

preLoaders:[{

test:/.jsx?$/,

include:[path.resolve( src/ )],

loader: isparta

}]

}

},

webpackMiddleware:{

noInfo:true

}

});

};

這裡相對於之前的karma.conf.js,主要有以下幾點區別:

由於webpack的打包功能,我們在測試代碼里直接import組件代碼,因此不再需要在files裏手動引入組件代碼

預處理裡面需要對每個測試文件都做webpack打包

添加webpack編譯相關配置,在編譯源碼時,需要定義preLoaders,並使用isparta-loader做代碼覆蓋率打點

添加webpackMiddleware配置,這裡noInfo作用是不需要輸出webpack編譯時那一大串信息

這樣配置基本上就完成了,跑一把./node_modules/karma/bin/karma start --single-run:

很好,結果符合預期。open coverage/lcov-report/index.html打開覆蓋率頁面:

鵝妹子音!!!直接對jsx代碼做的覆蓋率測試!這樣React組件的測試大體上就完工了

小結

前端的代碼測試主要難度是如何模擬各種各樣的瀏覽器環境,Karma給我們提供了很好地方式,對於本地有的瀏覽器能自動打開並測試,本地沒有的瀏覽器則提供直接訪問的頁面。前端尤其是移動端瀏覽器種類繁多,很難做到完美,但我們可以通過這種方式實現主流瀏覽器的覆蓋,保證每次上線大多數用戶沒有問題。

持續集成

測試結果有了,接下來就是把這些測試結果接入到持續集成之中。持續集成是一種非常優秀的多人開發實踐,通過代碼push觸發鉤子,實現自動運行編譯、測試等工作。接入持續集成後,我們的每一次push代碼,每個Merge Request都會生成對應的測試結果,項目的其他成員可以很清楚地了解到新代碼是否影響了現有的功能,在接入自動告警後,可以在代碼提交階段就快速發現錯誤,提升開發迭代效率。

持續集成會在每次集成時提供一個幾乎空白的虛擬機器,並拷貝用戶提交的代碼到機器本地,通過讀取用戶項目下的持續集成配置,自動化的安裝環境和依賴,編譯和測試完成後生成報告,在一段時間之後釋放虛擬機器資源。

開源的持續集成

開源比較出名的持續集成服務當屬Travis,而代碼覆蓋率則通過Coveralls,只要有GitHub賬戶,就可以很輕鬆的接入Travis和Coveralls,在網站上勾選了需要持續集成的項目以後,每次代碼push就會觸發自動化測試。這兩個網站在跑完測試以後,會自動生成測試結果的小圖片

Travis會讀取項目下的travis.yml文件,一個簡單的例子:

language定義了運行環境的語言,而對應的node_js可以定義需要在哪幾個Node.js版本做測試,比如這裡的定義,代表著會分別在最新穩定版、4.0.0、5.0.0版本的Node.js環境下做測試

而script則是測試利用的命令,一般情況下,都應該把自己這個項目開發所需要的命令都寫在package.json的scripts裡面,比如我們的測試方法./node_modules/karma/bin/karma start --single-run就應當這樣寫到scripts 里:

{

"scripts":{

"test":"./node_modules/karma/bin/karma start --single-run"

}

}

而after_script則是在測試完成之後運行的命令,這裡需要上傳覆蓋率結果到coveralls,只需要安裝coveralls庫,然後獲取lcov.info上傳給Coveralls即可

更多配置請參照Travis 官網介紹

這樣配置後,每次push的結果都可以上Travis和Coveralls看構建和代碼覆蓋率結果了

小結

項目接入持續集成在多人開發同一個倉庫時候能起到很大的用途,每次push都能自動觸發測試,測試沒過會發生告警。如果需求採用Issues+Merge Request來管理,每個需求一個Issue+一個分支,開發完成後提交Merge Request,由項目Owner負責合併,項目質量將更有保障

總結

這裡只是前端測試相關知識的一小部分,還有非常多的內容可以深入挖掘,而測試也僅僅是前端流程自動化的一部分。在前端技術快速發展的今天,前端項目不再像當年的刀耕火種一般,越來越多的軟體工程經驗被集成到前端項目中,前端項目正向工程化、流程化、自動化方向高速奔跑。還有更多優秀的提升開發效率、保證開發質量的自動化方案亟待我們挖掘。

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

如果你的產品停止成長,你該怎麼做?
流形-大數據浪潮下的前端工程師
解剖react組件的多種寫法與演進
漫談前端體系建設
關於載入設計,你要知道的8種策略和4種樣式

TAG:前端早讀課 |

您可能感興趣

聊一聊 網紅燈具們的前半生(上)
聊一聊動漫中那些末日下的人性!
聊一聊數字電路中時鐘抖動
聊一聊電腦散熱器進化
細細聊一聊坐洋的前世今生
聊一聊智慧城市的前世今生
聊一聊清明節上墳的講究
聊一聊:我想下班
「文鴛綉履」聊一聊中華足下的文化演變
聊一聊紫外線
今天聊一聊巨型萌貨大貓的進化史和共同點~
今天聊一聊巨型萌貨大貓的進化史和共同點
聊一聊戰術裝備上的激光切割技術
今天聊一聊「潛規則」
聊一聊:來聊聊你心中的電子產品鄙視鏈
「運動健康+通話」二合一,聊一聊華為B5手環
今天聊一聊手錶的上手效果及簡單參數
心平靜氣聊一聊方臉妹的出路
聊一聊歷史上真實的武松
聊一聊衝突管理