原版地址:http://docs.angularjs.org/guide/dev_guide.unit-testingjavascript
javascript是一門動態類型語言,這給她帶來了很強的表現能力,但同時也使編譯器幾乎不能給開發者提供任何幫助。由於這個緣由,咱們感覺到編寫任何javascript代碼都必須有一套強大完整的測試。angular擁有許多功能,讓咱們更加容易地測試咱們的應用。咱們應該沒有藉口不去寫測試(這個嘛……)。html
1、 It is all about NOT mixing concerns(所有都關於避免代碼關係變得複雜……)java
單元測試,正如名稱那樣,是關於測試單個「單元」的代碼。單元測試努力解答這些問題:我對邏輯的考慮是否已經正確?排序方法得出的結果是否正確?爲了解答這些問題,將這些問題獨立出來顯得尤爲重要。這是由於當咱們在測試排序方法的時候,咱們不想關心其餘相關的片斷,例如DOM元素或者發起XHR請求獲取數據等。明顯地,一般比較難作到在典型的項目中單獨調用一個函數。致使這個問題的緣由是,開發者一般把關係弄得很複雜,最終讓一個代碼片斷看起來能夠作全部事情。它經過XHR獲取數據,對數據進行排序,而後操縱DOM。與angular一塊兒,咱們能夠更加容易地寫出較好的代碼,因此angular爲咱們提供XHR(咱們能夠模擬它)的依賴注入,angular還建立容許咱們對model進行排序而不須要操做DOM的抽象。因此,到最後,咱們能夠簡單地寫一個排序方法,而後經過測試用例建立數據集合,供排序方法測試時使用,而後判斷結果model是否符合預期。測試無須等待XHR、者建立對應的DOM和判斷函數是否正確操做DOM。angular的核心思想包含代碼的可測試性,但同時也要求咱們去作正確的事情。angular致力於簡化作正確事情的方法,但angular不是魔法,這意味着咱們若是不遵循如下的幾點,咱們最終可能會得出一個不可測試的應用。angularjs
1. Dependency Injectexpress
有許多辦法能夠得到依賴的資源:1)咱們可使用new操做符;2)咱們使用一個衆所周知的方式,被稱爲」 全局單例」;3)咱們能夠向registry service請求(但咱們如何取得一個registry?能夠查看後面的章節);4)咱們能夠期待它會被傳遞過來。bootstrap
上面列出的方法中,只有最後一個是可測試的,讓咱們看看爲何:api
1) Using the new operator服務器
使用new操做符時基本上沒有錯誤,但問題是經過new調用構造函數將會永久地將調用方與type綁定起來。舉個例子,咱們嘗試實例化一個XHR對象,以讓咱們能夠從服務器得到一些數據。網絡
function MyClass() { this.doWork = function() { var xhr = new XRH(); xhr.open(method,url,true); xhr.onreadystatechange = function() {…}; xhr.send(); } }
問題來了,在測試時,咱們一般須要實例化一個能夠返回測試數據或者網絡錯誤的虛擬的XHR。經過調用new XHR(),咱們永久地綁定了真實的XHR,而且沒有一個很好的方法去替代它。固然,有一個糟糕的補救辦法,有不少理由能夠證實那是一個糟糕的想法:app
var oldXHR = XHR; XHR = new MockXHR() {}; myClass.doWork(); //判斷MockXHR是否經過正常的參數進行調用 XHR = oldXHR;//若是忘了這一步,很容易會發生悲催的事情。
2) Global look-up
解決問題的另一個方法是在一個衆所周知的地方獲取依賴的資源。
function MyClass() { this.doWork = function() { global.xhr({…}); }; }
沒有建立新依賴對象的實例的狀況下,問題基本上與new一致,除了那個悲催的補丁之外,沒有一個很好的方法能夠再測試時攔截global.xhr的調用。測試的最基本的問題是global變量須要改成調用虛擬的方法而被修改。想進一步瞭解它的壞處,能夠參觀這裏:http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
上面的代碼比較難去測試,因此咱們必須修改global state:
var oldXHR = global.xhr; global.xhr = function mockXHR(){…}; var myClass = new MyClass(); //判斷MockXHR是否經過正常的參數進行調用 global.xhr = oldXHR;//若是忘了這一步,很容易會發生悲催的事情。
3) Service Registry
擁有一個包含全部service的registry的話,彷佛能夠解決問題,而後,在測試代碼中替換所須要的service。
function MyClass() { var serviceRegistry = ???; this.doWork = function() { var xhr = serviceRegistry.get(「xhr」); … }; }
可是,serviceRegistry來自哪裏?if it is: * new-ed up, the the test has no chance to reset the services for testing * global look-up, then the service returned is global as well (but resetting is easier, since there is only one global variable to be reset)(這裏後面的文字跟亂碼同樣……沒看懂)
根據這個方法,將上面的Class修改成以下的方式:
var oldServiceLocator = global.serviceLocator; global.serviceLocator.set('xhr', function mockXHR() {}); var myClass = new MyClass(); myClass.doWork(); //判斷MockXHR是否經過正常的參數進行調用 global.serviceLocator = oldServiceLocator; //若是忘了這一步,很容易會發生悲催的事情。
4) Passing in Dependencies
最後,依賴資源能夠被傳入。
function MyClass(xhr) { this.doWork = function() { xhr({…}); }; }
這個是首選的方式,由於代碼無須理會xhr是從哪來的,也不關心誰建立了傳進來的xhr。所以,類的建立者與類的使用者能夠分開編碼,這將建立的責任從邏輯中分離出來,這就是依賴注入的概述。
這個class很容易測試,在測試中咱們能夠這樣寫:
function xhrMock(args) {…} var myClass = new MyClass(xhrMock); myClass.doWrok(); //作一些判斷…… 經過這個測試代碼,咱們能夠意識到沒有任何全局變量被破壞。
angular附帶的dependency-injection(http://www.cnblogs.com/lcllao/archive/2012/09/23/2699401.html),經過這種方式編寫的代碼,更加容易編寫測試代碼,若是咱們想編寫可測試性強的代碼,咱們最好使用它。
2. Controllers
邏輯使每個應用都是惟一的,這就是咱們想去測試的。若是咱們的邏輯裏面混雜着DOM的操做,這將會跟下面的例子同樣難測試:
function PasswordController() { // 獲取DOM對象的引用 var msg = $('.ex1 span'); var input = $('.ex1 input'); var strength; this.grade = function() { msg.removeClass(strength); var pwd = input.val(); password.text(pwd); if (pwd.length > 8) { strength = 'strong'; } else if (pwd.length > 3) { strength = 'medium'; } else { strength = 'weak'; } msg.addClass(strength).text(strength); } }
上面的代碼在測試時會遇到問題,由於它須要咱們的執行測試時候,須要有正確的DOM。測試代碼會以下:
var input = $('<input type="text"/>'); var span = $('<span>'); $('body').html('<div class="ex1">').find('div').append(input).append(span); var pc = new PasswordController(); input.val('abc'); pc.grade(); expect(span.text()).toEqual('weak'); $('body').html('');
在angular中,controller嚴格地將DOM操做邏輯分離出來,將大大下降編寫測試用例的難度,看看下面的例子:
function PasswordCntrl($scope) { $scope.password = ''; $scope.grade = function() { var size = $scope.password.length; if (size > 8) { $scope.strength = 'strong'; } else if (size > 3) { $scope.strength = 'medium'; } else { $scope.strength = 'weak'; } }; }
測試代碼直截了當:
var pc = new PasswordController($scope); pc.password('abc'); pc.grade(); expect($scope.strength).toEqual('weak');
值得注意的是,測試代碼不只僅更加間斷,並且更加容易追蹤。咱們一直說測試用例是在講故事,而不是判斷其餘不相關的東西。
3. Filters
filter(http://docs.angularjs.org/api/ng.$filter)是用於將數據轉換爲對用戶友好的格式。它們很重要,由於它們將轉換格式的責任從應用邏輯中分離出來,進一步簡化了應用邏輯。
myModule.filter('length', function() { return function(text){ return (''+(text||'')).length; } }); var length = $filter('length'); expect(length(null)).toEqual(0); expect(length('abc')).toEqual(3);
4. Directives
5. Mocks
6. Global State Isolation
7. Preferred way of Testing
8. JavascriptTestDriver
9. Jasmine
10. Sample project
沒錯,你沒有眼花……官網文檔竟然太監了!……期待更新……順帶說一下,Angular學習筆記—Guide系列暫告一段落!