做者:Philip Walton
譯者:Yeaseon
原文連接:Learning How to Set Up Automated, Cross-browser JavaScript Unit Testingjavascript
譯文僅供我的學習,不用於任何形式商業目的,轉載請註明原做者、文章來源、翻譯做者及連接,版權歸原文做者全部。css
咱們都知道在多個瀏覽器中測試咱們的代碼是多麼的重要。至少在咱們發佈第一個項目的時候,我認爲咱們在網絡開發社區作大部分工做仍是至關不錯的。html
咱們作的不夠好的工做是測試代碼時每一次作出的改變。java
我我的對此感到很慚愧。我已經把「學習如何構建自動化、跨瀏覽器的JavaScript的單元測試」列在個人年度to-do清單中,但我每一次坐下來真正想要作的時候,我又退卻了。雖然我確定這一部分緣由是由於個人懶惰,同時我認爲這也是因爲缺少良好的可用信息在這個主題上。node
有許多工具和框架(例如 Karma)宣稱「要使自動化的JavaScript測試變得簡單」,但以個人經驗看來這些工具引入的複雜性比他們擺脫的複雜性更多。在個人工做經驗中,若是你是一個專家這些工具「能工做」的很好,但對於一個初學者是很糟糕的。我想要真正瞭解的是這個流程是如何在引擎中工做的,以便在它出現問題的時候(總會出現問題的),我能解決它。webpack
對我來講,充分了解這些是如何工做的最好方法就是嘗試從頭開始從新建立它。因此我決定去構建我本身的測試工具,而後把個人所學分享到社區中。git
在我解釋自動化過程以前,我認爲最重要的是確保咱們都在同一頁面上進行手工測試工做。github
畢竟,自動化是關於使用機器來關閉負載的重複部分的現有工做流程。若是你在充分理解手工過程以前嘗試去開始自動化,它也不會像你理解了自動化過程同樣。web
在手工過程當中,你寫了一個你的測試文件,它可能看起來像是:chrome
var assert = require('assert'); var SomeClass = require('../lib/some-class'); describe('SomeClass', function() { describe('someMethod', function() { it('accept thing A and transforms it into thing B',function() { var sc = new SomeClass(); assert.equal(sc.someMethod('A'), 'B'); }); }); });
這個例子用了Mocha和Node.js 資源模塊,可是重要的不是你是用的測試庫或者斷言庫,它可使任意一個。
在Mocha中運行Node.js,在你終端經過命令行你就能運行這個測試:
mocha test/some-class-test.js
你須要一個帶有<script>
標籤的HTML文件加載這段腳本,才能在瀏覽器運行這個測試,瀏覽器並不認識require
聲明,你須要一個像是browserify或者webpack的模塊打包工具去解決這些依賴。
browserify test/*-test.js > test/index.js
像是browserify或是webpack的模塊打包工具的好處就是它能整合你的全部測試(也包括依賴)到一個單一的文件中,這樣就能很容易加載到你的測試頁面。
一個用Mocha寫的典型測試文件看起來像是這樣的:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Tests<title> <link href="../node_modules/mocha/mocha.css" rel="stylesheet" /> <script src="../node_modules/mocha/mocha.js"></script> </head> <body> <!-- A container element for the visual Mocha results --> <div id="mocha"></div> <!-- Mocha setup and initiation code --> <script> mocha.setup('bdd'); window.onload = function() { mocha.run(); }; </script> <!-- The script under test --> <script src="index.js></script> </body> </html>
若是你不使用Node.js,那麼你的起點看起來已經很像這個HTML文本了,惟一不一樣的是你的依賴可能須要列成一個個單獨的<script>
標籤。
若是一個測試因爲是斷言不正確,你的斷言庫任什麼時候間都會拋出一個錯誤,這個時候你的測試框架就能發現這個錯誤。測試框架運行在每一個測試的try/catch中來捕獲可能會拋出的錯誤,這些錯誤報告會顯示在你的頁面中或是在console中顯示這些log。
大多數的測試框架(像是Mocha)將會提供鉤子,這樣你就能在測試過程當中讓頁面中的其餘腳本訪問測試結果。這是一個自動化測試過程的一個關鍵特徵,由於爲了自動化工做,自動化腳本須要可以提取測試腳本的結果。
在瀏覽器中手工運行測試的最大好處是,假如你的一個測試失敗了你能用瀏覽器的開發者工具去調試它。
像是這樣的一個簡單例子:
describe('SomeClass', () => { describe('someMethod', () => { it('accepts thing A and transforms it into thing B', () => { const sc = new SomeClass(); debugger: assert.equal(sc.someMethod('A'), 'B'); )}; )}; )};
如今當你從新打包並刷新瀏覽器打開開發者工具,你就能夠經過你的代碼,很容易定位到問題的根源所在。
相比之下,大多數流行的自動化測試框架使這變得很困難!它們提供的方便之處是它們捆綁了你的單元測試而且爲你建立一個宿主的HTML頁面。
在你的任何一個測試都不會失敗的時候,這是很好的方式。由於當它們這樣作時,就沒有辦法輕鬆地reproduce和本地調試。
手工流程有它的有點,同時也有一些缺點。打開幾個瀏覽器去運行測試,每次你想作出改動的時候都會變得繁瑣且容易出錯。更不用說,咱們大部分人沒有安裝每個瀏覽器的每個版本到咱們的本地開發機器上。
若是你在認真的測試你的代碼,並但願確保它的每個變化都作適當的,那麼你須要自動化這個流程。
不管你是多麼的自覺,手動測試是太容易忘記或忽略,最終它不會充分利用你的時間。
可是自動化測試一樣也有它的不足。過於頻繁的自動測試工具引入了一個全新的問題。輕微不一樣的構建,測試就會變得不一樣,測試失敗的話面臨的將是痛苦的調試。
當我計劃如何構建個人自動化測試系統的時候,我不想再掉進這個陷阱和失去手工測試流程的便利性。因此我決定在開始以前作一個需求列表。
畢竟,一個自動化系統若是引入了新的使人頭疼的麻煩和複雜性,那它就不是一個成功的自動化系統。
我須要可以使用命令行運行測試
我須要可以在本地調試失敗測試
我須要全部必需的依賴經過npm
運行測試就能被安裝,因此任何人查看個人代碼就能很簡單的運行,經過
npm install && npm test
我須要運行在CI機器上的測試流程和運行在個人開發機器同樣簡單。這樣構建方式是相同的,而且無需檢查新的變化就能調試錯誤。
我須要全部的測試我(或者任意人)提交新的變化或者拉取請求都能在任什麼時候間自動化運行。
有了這個粗略的列表以後,下一步就是深刻到在主流的雲測試如何自動化,跨瀏覽器測試的工做。
有不少雲測試的供應商,每一個供應商都有本身的長處和短處。我是一個開源做者,因此我只看那些提供開源項目的供應商,它們之中,只有Sauce Labs是惟一一個不須要我郵箱支持就能啓動一個新的開源帳戶。
更令我吃驚的是當我真正開始鑽研Sauce Labs關於JavaScript單元測試的文檔是有多麼簡單。因爲好多測試框架都有聲稱讓單元測試變得簡單,我認爲這真的很難!
我前面強調了一點就是,我不想個人自動化流程和個人手工流程有什麼根本上的不一樣。事實證實,Sauce Labs提供的自動化方法真的很像個人手工方法。
這裏是所涉及的步驟:
你給Sauce Labs一個你測試頁面的URL以及你要運行的測試的瀏覽器/平臺列表。
Sauce Labs使用selenium webdriver去加載你給它的每個瀏覽器和平臺的組合的測試頁面。
WebDriver檢查網頁是否測試失敗,並將結果存儲。
Sauce Labs將有用的結果給你。
這真的很簡單。
我錯誤地假設你不得不把你的JavaScript代碼給Sauce Labs,而且它將會運行在它的機器上,而不是它們只是去訪問你給它們的URL。這樣的話看起來就像手工流程了;惟一不一樣的是Sauce Labs去打開全部的瀏覽器併爲你記錄下結果。
Sauce Labs有兩個運行單元測試的API方法:
StartJS Unit Tests方法在你指定的瀏覽器/平臺啓動一個測試頁面。
文檔給了一個使用curl
的例子:
curl https://saucelabs.com/rest/v1/SAUCE_USERNAME/js-tests \ -X POST \ -u SAUCE_USERNAME:SAUCE_ACCESS_KEY \ -H 'Content-Type: application/json' \ --data '{"url": "https://example.com/tests.html", "framework": "mocha", "platforms": [["Windows 7", "firefox", "27"], ["Linux", "chrome", "latest"]]}'
由於這是JavaScript單元測試,我將給你一個使用node模塊request的例子,若是你正在用Node.js它可能更接近你最終要作的:
request({ url: `https://saucelabs.com/rest/v1/${username}/js-tests`, method: 'POST', auth: { username: process.env.SAUCE_USERNAME, password: process.env.SAUCE_ACCESS_KEY }, json: true, body: { url: 'https://example.com/tests.html', framework: 'mocha', platforms: [ ['Windows 7', 'firefox', '27'], ['Linux', 'chrome', 'latest'] ] } }, (err, response) => { if (err) { console.error(err); } else { console.log(response.body); } });
你注意到body中的framework: 'mocha'
。Sauce Labs供應商支持許多主流的JavaScript單元測試框架,包括 Mocha,Jasmine,Qunit和YUI。「支持」意味着Sauce Labs的webdriver客戶端知道去哪獲取測試結果。
若是你沒有使用上面提到的測試框架,你能能夠經過設置framework: 'custom'
,Sauce Labs將會代替找到的全局變量window.global_test_results
。格式化的結果被列在文檔中的自定義框架一節中。
讓Mocha測試結果對於Sauce Labs的webdriver客戶端有用
儘管你在最初的請求中告訴Sauce Labs你在使用Mocha,你仍然須要去更新你的HTML頁面,去存儲Sauce Labs能訪問的全局變量的測試結果。
爲你的HTML頁面增長Mocha支持:
<script> mocha.setup('bdd'); window.onload = function() { mocha.run(); }; </script>
作一些事情,像下面這樣:
<script> mocha.setup('bdd'); window.onload = function() { var runner = mocha.run(); var failedTests = []; runner.on('end', function() { window.mochaResults = runner.stats; window.mochaResults.reports = failedTests; }); runner.on('fail', logFailure); function logFailure(test, err){ var flattenTitles = function(test){ var titles = []; while (test.parent.title){ titles.push(test.parent.title); test = test.parent; } return titles.reverse(); }; failedTests.push({ name: test.title, result: false, message: err.message, stack: err.stack, titles: flattenTitles(test) }); }; }; </script>
在上面的代碼和默認的Mocha模板中惟一不一樣的是分配給測試結果的變量名,就像Sauce Labs指望的格式同樣叫作window.mochaResults
。由於這個新的代碼不會影響正在瀏覽器中運行的手工測試,你不妨就開始使用它做爲默認的Mocha模板。
再次強調一點,當Sauce Labs「運行」你的測試時,它並無作任何事,它只是單純的訪問一個頁面,等到發現一個window.mochaResults
對象,而後記錄下這個結果。
肯定你的測試經過仍是失敗
StartJS Unit Tests 方法會告訴Sauce Labs去挨個在你指定的瀏覽器/平臺運行測試,可是它不會返回測試的結果。
它返回全部工做隊列中的ID,響應看起來像是這樣的:
{ "js tests": [ "9b6a2d7e6c8d4fd2afeeb0ff7e54e694", "d38688ec7256497da6966f4523ddee76", "14054e68ccd344c0bed77a798a9ce1e8", "dbc54181f7d947458f52201ea5fcb901" ] }
要肯定你測試經過仍是失敗,你要調用GetJS Unit Status方法,它接手一個工做隊列而且返回當前每一個工做的工做狀態。
這個想法是你要按期調用這個方法,知道全部工做都完成。
request({ url: `https://saucelabs.com/rest/v1/${username}/js-tests/status`, method: 'POST', auth: { username: process.env.SAUCE_USERNAME, password: process.env.SAUCE_ACCESS_KEY }, json: true, body: jsTests, // The response.body from the first API call. }, (err, response) => { if (err) { console.error(err); } else { console.log(response.body); } });
響應的結果看起來像是這樣:
{ "completed": false, "js tests": [ { "url": "https://saucelabs.com/jobs/75ac4cadb85e415fae957f7811d778b8", "platform": [ "Windows 10", "chrome", "latest" ], "result": { "passes": 29, "tests": 30, "end": {}, "suites": 7, "reports": [], "start": {}, "duration": 97, "failures": 0, "pending": 1 }, "id": "1f74a237d5ba4a47b5a42570ae1e7999", "job_id": "75ac4cadb85e415fae957f7811d778b8" }, // ... the rest of the jobs ] }
一旦response.body.complete
屬性值爲true
,就表示你的測試已經運行完成,而後你就能夠經過檢查每一個工做流程的經過仍是失敗。
我已經解釋過Sauce Labs「運行」你的測試經過訪問一個URL。固然,這意味着這個URL必須是公開在網絡上可訪問的連接。
有一個問題就是若是你的測試服務啓動在localhost
。
有不少解決這個問題的方案,包括Sauce Connect(官方推薦的一種),這是一個由Sauce Labs建立的代理服務器,在Sauce Labs虛擬機和本地主機之間開啓一個安全鏈接。
Sauce Labs是處於安全性的考慮被設計的,而且使得外部沒法得到你的代碼。它的缺點就是十分複雜的設置與使用。
若是你的代碼涉及到安全性,它可能值得你去弄清楚Sauce Labs;若是不是的話,有許多類似的方案去更簡單的解決這個問題。
我選擇的方案是ngrok
ngrok
ngrok是一個用於建立安全隧道鏈接工具。它給你一個公共的URL到web服務器運行在你的本地機器上,確切的是你須要運行測試在Sauce Labs上。
若是你在虛擬機上進行開發或手動測試,你可能已經據說過ngrok,若是沒有,那你應該去查閱一下了,它是極其有用的工具。
在你的機器上安裝ngrok像是下載二進制文件,而後添加到你的路徑中同樣簡單;若是你將會在Node中使用ngrok,你也須要經過npm安裝它。
npm install ngrok
你能夠用下面的代碼以編程方式從Node中開始ngrok進程:
const ngrok = require('ngrok'); ngrok.connect(port, (err, url) => { if (err) { console.error(err); } else { console.log(`Tests now accessible at: ${url}`); } });
只要你有一個公共的URL能訪問你的測試文件,用Sauce Labs跨瀏覽器測試你的本地代碼會變得十分容易。
這篇文章包含了不少主題,給人的印象是自動化的,跨瀏覽器的JavaScript單元測試是複雜的。但狀況並不是如此。
我從個人角度來看這篇文章-當我試圖去解決這個問題。而後回顧我以前的經驗,真正複雜的是缺乏解決整個流程如何工做的有效信息,和怎麼樣把全部的整合到一塊兒。
一旦你瞭解了全部的步驟,它很簡單。總結:
最初的手工流程
寫一個測試而後建立一個單一的HTML頁面去運行它。
在本地的一個或者兩個瀏覽器中運行這個測試,確保它能工做。
增長自動化流程
建立一個開源的Sauce Labs帳號,得到一個用戶名和訪問權限。
更新你的測試頁面源碼,以便Sauce Labs能經過JavaScript全局變量讀取測試結果。
用ngrok給你的本地測試頁面建立一個安全隧道,這樣就能在互聯網公開的訪問了。
調用StartJS Unit Tests接口方法列出你想測試的瀏覽器/平臺。
定時調用GetJS Unit Test Status方法知道工做完成。
報告結果。
我知道這篇文章開頭我談了不少關於你不須要一個框架來作自動化,跨瀏覽器的JavaScript單元測試,我如今仍然堅信這個。然而,儘管每一步都很簡單,你可能不想在每次都爲項目編寫代碼。
我想給個人不少老項目增長自動化測試,因此對我來講打包這些邏輯到個人模塊中是頗有意義的。
我推薦你嘗試實現一個你本身的框架,這樣你就能夠徹底理解它是如何工做的,但若是你沒有時間而且還想快速創建一個測試,我建議你使用我建立的庫Easy Sauce。
Easy Sauce是一個Node包和一個命令行工具,如今我爲我想作跨瀏覽器測試的每個JavaScript項目都使用這個包。
easy-sauce
命令能夠設置你的HTML測試文件的路徑(默認是/test/
)、開啓本地服務的端口(默認是1337
端口)和一系列的瀏覽器/平臺進行測試。easy-sauce
將會在Sauce Lab’s selenium cloud運行你的測試,將日誌打印在控制檯並經過合適的狀態碼告知你測試是否經過。
npm包使它變得更方便,easy-sauce
將會默認在package.json
文件中查找配置選項,因此你沒必要分別的存儲它們。好處是用戶更加明確的知道你的包支持瀏覽器/平臺。
對於easy sauce
完整的用法介紹,請查看Github文檔。
最後,我想強調的是我專門創建這個項目來解決個人須要。雖然我認爲這個項目對於不少開發人員都十分有用,但我沒有計劃把它變成一個功能齊全的測試解決方案。
在這篇文章的開始,我寫下了一系列的需求。在Easy Sauce的幫助下,我正努力的在任何項目中知足這些需求。
若是你尚未爲你的項目作自動化、跨瀏覽器的JavaScript單元測試,我鼓勵你給Easy Sauce一個嘗試的機會。即便你不想用Easy Sauce,你至少應該瞭解你本身的需求或更好地瞭解現有的工具。
Happy testing!
若是你能看到這裏,很感謝你的耐心閱讀。這是我翻譯的第一篇技術文檔,自身水平有限,因此翻譯總有不當與疏漏,若有發現還請您耐心評論指出。