通過確保您的應用程序經過測試,您可以減少代碼中發現的錯誤數量,提高應用程序的可維護性,并設計結構良好的代碼。
客戶端單元測試提出了與服務器端測試不同的挑戰。在處理客戶端代碼時,您會發現自己很難將應用程序邏輯與 DOM 邏輯分開,并且通常只是構建 JavaScript 代碼。幸運的是,有很多很棒的客戶端測試庫可以幫助測試您的代碼、創建有關測試覆蓋率的指標以及分析其復雜性。
為什么要測試?
首先,單元測試通常是一種通過確保應用程序按預期運行來減少錯誤的方法。除此之外,還有測試驅動開發 (TDD) 和行為驅動開發 (BDD) 的概念。
這兩種單元測試策略將幫助您在編寫應用程序邏輯之前編寫測試來設計應用程序。通過在編寫代碼之前編寫測試,您將有機會仔細思考應用程序的設計。
發生這種情況是因為當您編寫測試時,您基本上是在嘗試設計與代碼交互的 API,因此您可以更好地了解其設計。首先進行測試將很快顯示出設計中存在的任何缺陷,因為您正在編寫的測試代碼本質上使用了您正在編寫的代碼!
TDD 是一個代碼發現過程
您將了解到 TDD 可以幫助您在編寫代碼時發現代碼。 TDD 很快就可以概括為“紅、綠、重構”。這意味著,您編寫一個測試,編寫足夠的代碼首先使測試失敗。 然后,您編寫使測試通過的代碼。之后,你仔細思考你剛剛寫的內容并重構它。又好又簡單。
BDD 與 TDD 略有不同,更多地基于業務需求和規范。
客戶端測試
您應該測試客戶端代碼的原因有很多。如前所述,它將有助于減少錯誤,并幫助您設計應用程序。客戶端測試也很重要,因為它使您有機會獨立測試前端代碼,遠離演示文稿。換句話說,它的優點之一是您可以測試 JavaScript 代碼,而無需實際啟動應用程序服務器。您只需運行測試并確保功能正常運行,而無需四處點擊并進行測試。在許多情況下,只要正確設置測試,您甚至不需要訪問互聯網。
隨著 JavaScript 在現代 Web 開發中發揮如此重要的作用,學習如何測試代碼并減少錯誤進入生產代碼的機會非常重要。您的老板不喜歡這種情況發生,您也不應該!事實上,開始進行客戶端測試的一個好地方是圍繞錯誤報告編寫測試。當您沒有地方從頭開始時,這將使您能夠練習編寫測試。
測試客戶端代碼的另一個原因是,一旦存在一套測試并且對您的代碼有適當的覆蓋,當您準備好向代碼添加新功能時,您將能夠添加新功能功能,重新運行您的測試,并確保您沒有退化或破壞任何現有功能。
開始使用
如果您以前從未做過客戶端測試,那么開始進行客戶端測試可能會令人畏懼。客戶端測試最困難的部分之一是找出將 DOM 與應用程序邏輯隔離的最佳方法。這通常意味著您需要對 DOM 進行某種抽象。實現這一目標的最簡單方法是通過客戶端框架,例如 Knockout.js、Backbone.js 或 Angular.js,僅舉幾例。
使用此類庫時,您可以少考慮頁面在瀏覽器中的呈現方式,而多考慮應用程序的功能。不過,用簡單的 JavaScript 進行單元測試并不是不可能的。在這種情況下,如果您以 DOM 可以輕松抽象的方式設計代碼,您的生活將會輕松得多。
選擇測試庫
有很多不同的測試庫可供選擇,盡管三個領先者往往是 QUnit、Mocha 和 Jasmine。
Jasmine 和 Mocha 都來自 BDD 單元測試學派,而 QUnit 只是它自己的一個單元測試框架。
在本文的其余部分中,我們將探討使用 QUnit,因為它進入客戶端測試的門檻非常低。查看 QUnit 的詳細介紹以獲取更多信息。
使用 QUnit TDD 您的代碼
QUnit 入門非常簡單。您只需要以下 HTML:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="qunit.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="qunit.js"></script> <script src="../app/yourSourceCode.js"></script> <script src="tests.js"></script> </body> </html>
登錄后復制
對于接下來的幾個示例,假設我們正在構建一個小部件,您可以在文本框中輸入郵政編碼,然后它會使用 Geonames 返回相應的城市、州和縣值。它一開始只顯示郵政編碼,但一旦郵政編碼有五個字符,它就會從 Geonames 檢索數據。如果能夠找到數據,它將顯示更多包含結果城市、州和縣信息的字段。我們還將使用 Knockout.js。第一步是編寫失敗的測試。
在編寫第一個測試之前稍微思考一下設計,可能需要至少兩個 viewModel,所以這將是一個很好的起點。首先,我們將定義一個 QUnit 模塊和我們的第一個測試:
module("zip code retriever"); test("view models should exist", function() { ok(FormViewModel, "A viewModel for our form should exist"); ok(AddressViewModel, "A viewModel for our address should exist"); });
登錄后復制
如果你運行這個測試,它會失敗,現在你可以編寫代碼讓它通過:
var AddressViewModel = function(options) { }; var FormViewModel = function() { this.address = new AddressViewModel(); };
登錄后復制
這次您會看到綠色而不是紅色。像這樣的測試乍一看有點愚蠢,但它們很有用,因為它們迫使您至少思考設計的一些早期階段。
我們將編寫的下一個測試將適用于 AddressViewModel
的功能。從這個小部件的規范中我們知道,其他字段應該首先隱藏,直到找到郵政編碼的數據。
module("address view model"); test("should show city state data if a zip code is found", function() { var address = new AddressViewModel(); ok(!address.isLocated()); address.zip(12345); address.city("foo"); address.state("bar"); address.county("bam"); ok(address.isLocated()); });
登錄后復制
尚未編寫任何代碼,但這里的想法是 isLocated
將是一個計算的可觀察值,僅當郵政編碼、城市、州和縣時才返回 true
都是實話。所以,這個測試一開始當然會失敗,現在讓我們編寫代碼讓它通過。
var AddressViewModel = function(options) { options = options || {}; this.zip = ko.observable(options.zip); this.city = ko.observable(options.city); this.state = ko.observable(options.state); this.county = ko.observable(options.county); this.isLocated = ko.computed(function() { return this.city() && this.state() && this.county() && this.zip(); }, this); this.initialize(); };
登錄后復制
現在,如果您再次運行測試,您將看到綠色!
這是最基本的,如何使用 TDD 編寫前端測試。理想情況下,在每次失敗的測試之后,您應該編寫最簡單的代碼來使測試通過,然后返回并重構代碼。不過,您可以了解更多有關 TDD 實踐的知識,因此我建議您進一步閱讀并研究它,但前面的示例足以讓您考慮首先編寫測試。
使用Sinon.js模擬依賴關系
Sinon.js 是一個 JavaScript 庫,提供監視、存根和模擬 JavaScript 對象的功能。編寫單元測試時,您希望確保只能測試給定的代碼“單元”。這通常意味著您必須對依賴項進行某種模擬或存根以隔離正在測試的代碼。
Sinon 有一個非常簡單的 API 可以完成此操作。 Geonames API 支持通過 JSONP 端點檢索數據,這意味著我們將能夠輕松使用 $.ajax
。
理想情況下,您不必在測試中依賴 Geonames API。它們可能會暫時關閉,您的互聯網可能會中斷,并且實際進行 ajax 調用的速度也會變慢。詩乃前來救援。
test("should only try to get data if there's 5 chars", function() { var address = new AddressViewModel(); sinon.stub(jQuery, "ajax").returns({ done: $.noop }); address.zip(1234); ok(!jQuery.ajax.calledOnce); address.zip(12345); ok(jQuery.ajax.calledOnce); jQuery.ajax.restore(); });
登錄后復制
在此測試中,我們做了一些事情。首先,sinon.stub
函數實際上將代理 jQuery.ajax
并添加查看其被調用次數以及許多其他斷言的功能。正如測試所示,“應該僅在有 5 個字符時嘗試獲取數據”,我們假設當地址設置為“1234
”時,尚未進行 ajax 調用,然后將其設置為“12345
”,此時應進行 ajax 調用。
然后我們需要將 jQuery.ajax
恢復到其原始狀態,因為我們是單元測試的好公民,并且希望保持我們的測試原子性。保持測試的原子性非常重要,可以確保一個測試不依賴于另一測試,并且測試之間不存在共享狀態。然后它們也可以按任何順序運行。
現在測試已經編寫完畢,我們可以運行它,觀察它失敗,然后編寫向 Geonames 執行 ajax 請求的代碼。
AddressViewModel.prototype.initialize = function() { this.zip.subscribe(this.zipChanged, this); }; AddressViewModel.prototype.zipChanged = function(value) { if (value.toString().length === 5) { this.fetch(value); } }; AddressViewModel.prototype.fetch = function(zip) { var baseUrl = "http://www.geonames.org/postalCodeLookupJSON" $.ajax({ url: baseUrl, data: { "postalcode": zip, "country": "us" }, type: "GET", dataType: "JSONP" }).done(this.fetched.bind(this)); };
登錄后復制
在這里,我們訂閱郵政編碼的更改。每當它發生變化時,都會調用 zipChanged
方法。 zipChanged
方法將檢查 zip 值的長度是否為 5
。當到達 5
時,將調用 fetch
方法。這就是Sinon 存根發揮作用的地方。此時,$.ajax
實際上是一個Sinon存根。因此,在測試中 CalledOnce
將是 true
。
我們將編寫的最終測試是數據從 Geonames 服務返回時的情況:
test("should set city info based off search result", function() { var address = new AddressViewModel(); address.fetched({ postalcodes: [{ adminCode1: "foo", adminName2: "bar", placeName: "bam" }] }); equal(address.city(), "bam"); equal(address.state(), "foo"); equal(address.county(), "bar"); });
登錄后復制
此測試將測試如何將來自服務器的數據設置到 AddressViewmodel
上。運行一下,看到一些紅色。現在將其設為綠色:
AddressViewModel.prototype.fetched = function(data) { var cityInfo; if (data.postalcodes && data.postalcodes.length === 1) { cityInfo = data.postalcodes[0]; this.city(cityInfo.placeName); this.state(cityInfo.adminCode1); this.county(cityInfo.adminName2); } };
登錄后復制
fetched方法只是確保從服務器傳來的數據中有一個postalcodes
數組,然后在viewModel
上設置相應的屬性。
看看這現在有多容易了嗎?一旦你掌握了執行此操作的流程,你就會發現自己幾乎不想再進行 TDD。您最終會得到可測試的漂亮小函數。您強迫自己思考代碼如何與其依賴項交互。現在,當代碼中添加其他新需求時,您可以運行一套測試。即使您錯過了某些內容并且代碼中存在錯誤,您現在也可以簡單地向套件添加新測試,以證明您已經修復了錯誤!它實際上最終會讓人上癮。
測試覆蓋率
測試覆蓋率提供了一種簡單的方法來評估單元測試測試了多少代碼。達到 100% 的覆蓋率通常很困難且不值得,但請盡您所能使其盡可能高。
更新且更簡單的覆蓋庫之一稱為 Blanket.js。將它與 QUnit 一起使用非常簡單。只需從他們的主頁獲取代碼或使用 Bower 安裝即可。然后將毯子添加為 qunit.html
文件底部的庫,然后將 data-cover
添加到您想要進行覆蓋率測試的所有文件。
<script src="../app/yourSourceCode.js" data-cover></script> <script src="../js/lib/qunit/qunit/qunit.js"></script> <script src="../js/lib/blanket/dist/qunit/blanket.js"></script> <script src="tests.js"></script> </body>
登錄后復制
完成。超級簡單,現在您將在 QUnit 運行程序中獲得一個用于顯示覆蓋范圍的選項:
在此示例中,您可以看到測試覆蓋率并不是 100%,但在這種情況下,由于代碼不多,因此很容易提高覆蓋率。您實際上可以深入了解尚未涵蓋的確切功能:
在這種情況下,FormViewModel
從未在測試中實例化,因此缺少測試覆蓋率。然后,您可以簡單地添加一個新測試來創建 FormViewModel
的實例,并且可能編寫一個斷言來檢查 address
屬性是否存在并且是 instanceOf
AddressViewModel
。
然后您將很高興看到 100% 的測試覆蓋率。
復雜性測試
隨著您的應用程序變得越來越大,能夠對 JavaScript 代碼運行一些靜態分析是件好事。 Plato 是一個在 JavaScript 上運行分析的好工具。
您可以通過 npm
安裝來運行 plato
:
npm install -g plato
登錄后復制
然后您可以在 JavaScript 代碼目錄上運行 plato
:
plato -r -d js/app reports
登錄后復制
這將在位于“js/app
”的所有 JavaScript 上運行 Plato,并將結果輸出到 reports
。 Plato 對您的代碼運行各種指標,包括平均代碼行數、計算的可維護性分數、JSHint、難度、估計錯誤等等。
在上一張圖片中沒有太多可看的內容,僅僅是因為對于我們一直在處理的代碼來說,只有一個文件,但是當您開始使用具有大量文件的大型應用程序時,幾行代碼,您會發現它為您提供的信息非常有用。
它甚至會跟蹤您運行它的所有時間,以便您可以查看統計數據如何隨時間變化。
結論
雖然測試客戶端似乎是一個困難的提議,但現在有很多很棒的工具可以使用,使它變得超級簡單。本文僅僅觸及了當今所有使客戶端測試變得容易的事情的表面。這可能是一項乏味的任務,但最終您會發現,擁有測試套件和可測試代碼的好處遠遠超過它。希望通過此處概述的步驟,您將能夠快速開始測試客戶端代碼。
以上就是請記住保護您的客戶端!的詳細內容,更多請關注www.92cms.cn其它相關文章!