【AngularJS的概念及其單元測試】之基本概念

1、AngularJS基本概念

1)AngularJS框架的核心概念

AngularJS框架的核心概念是MVC架構模式(或者說MVVM,Model-View-ViewModel,這兩個模式差異不大)。MVC架構模式能夠講整個應用劃分紅三個徹底不相關的獨立模塊:
(1)模型Model:是整個應用的驅動力。通常來講,指的是應用從服務器端獲取的數據,任何在UI上看到的數據都是從模型或模型的子集中獲取的。
(2)視圖View:是用戶能夠瀏覽並與之交互的UI界面。它是動態的,基於當前系統的模型。
(3)控制器Controller:表明業務邏輯和表現層。控制器負責具體實現方式來決定將哪些模型展示在視圖中,所以它能夠被看做是一個視圖模型,或者展示器(presenter)。html

這樣將應用分拆成獨立的子單元的好處是:
(1)每一個單元只負責作一件事情,符合單一職責原則。模型層負責數據操做,視圖層展示UI界面,控制器負責業務邏輯。
(2)每一個單元之間相互獨立,這使得模塊化、可重用性和可維護性大大提升。java

2)AngularJS的哲學

(1)數據驅動(經過數據綁定實現)

一種情形:jquery

HTML:Hello <span id="name"></span>
Javascript:
var updateNameUI = function(name){
    $('#name').text(name);
}
// 首次加載數據時
updateNameUI(user.name);
// 當數據變化時從新顯示
updateNameUI(updatedName);

如上,顯示數據須要找到對應的UI元素並更新它的innerText,每次名稱變化,都不得不調用以此該函數。而AngularJS採用模型驅動應用,經過數據綁定來實現,既有單向綁定,也有雙向綁定。angularjs

把數據綁定到HTML上,AngularJS會負責正確的將數據傳遞給UI,一旦數據變化,AngularJS會檢查到變化並自動更新UI。單向綁定的實現方式:Hello <span ng-bind="name"></span>Hello <span>{ {name} }</span>web

另外一種情形:正則表達式

<form name="myForm" onsubmit="submitData()">
    <input type="text" id="nameField" />
    <input type="text" id="emailField" />
</form>
function setUserDetails(userDetails){
    $('#nameField').value(userDetails.name);
    $('#emailField').value(userDetails.email);
}
function getUserDetails(){
    return {
        name: $('#nameFeild').value(),
        email: $('#emailFeild').value()
    }
}
var submitData = function(){
    makeXhrRequest('http://url', getUserDetails);
}

上面這種情形,除了頁面佈局和模板以外,還須要編寫代碼來控制業務邏輯/控制器與UI之間的數據雙向傳遞,須要本身手動更新數據和獲取數據。AngularJS提供了雙向數據綁定,不須要再編寫額外的代碼區傳遞數據。雙向數據綁定可讓控制器和UI共享一個數據模型,任何一邊修改了數據,都會致使另外一邊自動更新。實現方式:chrome

<form name="myForm" onsubmit="submitData()">
    <input type="text" ng-model="uer.name" />
    <input type="text" ng-model="user.email" />
</form>

(2)聲明

未經擴展的原生HTML模板很難體現出頁面的構成,好比下面的結構:npm

<ul calss="nav nav-tabs">
    <li>Home</li>
    <li class="selected">Profile</li>
</ul>
<div class="tab1">Some Content Here</div>
<div class="tab2"><input id="startDate" type="text" /></div>

AngularJS定義了聲明範式,直接在HTML裏聲明你想要什麼就能夠了。AngularJS經過聲明實現上述功能,加強HTML,以下代碼(IE8及如下版本不支持擴展HTML標籤):json

<tabs>
    <tab title="Home">Some Content Here</tab>
    <tab title="Profile"><input type="text" datepicker ng-model="startDate" /></tab>
</tabs>

這樣的作法的好處是:

  • 這是聲明式的,看到HTML代碼便可明白其結構,含有兩個標籤頁,其中一個含有日曆控件。
  • 全部的功能都進行封裝在指令中。

(3)概念分離

AngularJS的全部應用均可以歸結於MVC架構:

  • 不一樣部分之間的概念分離的很是清晰。業務邏輯只在控制器中定義,而渲染則限於視圖。
  • 其餘成員很容易理解你的代碼,由於代碼結構和模式都是既定的。
  • 須要進行修改時,只須要單獨修改相關的部分,而不會影響其餘模塊。
  • AngularJS又並不徹底是MVC架構的,它的控制器並不直接持有視圖的引用,這是一個進步,由於它讓控制器可以獨立於視圖。在測試的時候就不須要再建立DOM節點了。

(4)依賴注入

依賴注入是指當咱們須要某個具體的控制器或者服務時,並不用直接在代碼中用new操做符或函數顯示建立實例(相似DatabaseFactory.getInstance()),而是發送請求以獲取它的全部依賴關係。
這樣作的好處是咱們並不須要關心如何構建這些依賴關係以及在開始以前就明確咱們須要什麼。

(5)可擴展性

如指令,極大地擴展了瀏覽器和HTML的功能。

3)angular應用中的四大金剛

控制器、服務、過濾器、指令

  • 控制器:讓數據與用戶界面交互,也能夠處理簡單的頁面風格和表現邏輯。
  • 服務:用來建立經常使用的業務邏輯,它能共享於全部的控制器。
  • 過濾器:用於處理數據,以及將數據格式化後呈現給用戶。
  • 指令:在指令中能夠操做DOM,能夠操做和渲染可重用的UI組件。

4)AngularJS的可測試性

AngularJS從控制器、服務、指令到視圖、頁面遷移都被設計成可測試性的。AngularJS中的控制器和視圖是相互獨立的,並且依賴注入的部分一樣具備高可測試性。Karma提供了單元測試環境,Protractor是一個基於WebDriver的端到端測試環境。



2、單元測試的基本概念

1)對單元測試的理解

在百度百科上的解釋:

  • 單元unit:單元就是相對獨立的功能模塊。一個完整的、模塊化的程序都是由許多單元構成,單元完成本身的任務、而後與其餘單元進行交互,最終協同完成整個程序的功能。

  • 測試test:測試就是判斷測試對象對於某個特定的輸入有沒有預期的輸出。

工程上的一個共識是:若是程序的每一個模塊都是正確的,模塊與模塊之間的鏈接是正確的,那麼程序基本上就是正確的。
因此單元測試就是一種保證構成程序的每一個模塊的正確性,從而保證整個程序的正確性的方法論。單元測試(優先)的目的就是首先保證一個系統的基本組成單元、模塊(如對象以及對象中的方法)能正常工做,這是一種分而治之中的bottom-up思想。

因此,爲何須要單元測試?

  • 正確性:測試能夠驗證代碼的正確性,在上線前作到內心有底。
  • 自動化:固然手工也能夠測試,經過console能夠打印出內部信息,可是這是一次性的事情,下次測試還須要從頭來過,效率不能獲得保證。經過編寫測試用例,能夠作到一次編寫,屢次運行。
  • 解釋性:測試用例用於測試接口、模塊的重要性,那麼在測試用例中就會涉及如何使用這些API。其餘開發人員若是要使用這些API,那閱讀測試用例是一種很好地途徑,有時比文檔說明更清晰。
  • 驅動開發,指導設計:代碼被測試的前提是代碼自己的可測試性,那麼要保證代碼的可測試性,就須要在開發中注意API的設計,TDD將測試前移就是起到這麼一個做用。
  • 保證重構:互聯網行業產品迭代速度很快,迭代後必然存在代碼重構的過程,那麼要怎樣才能保證重構後代碼的質量呢?有測試用例作後盾,就能夠大膽的進行重構,重構後只需運行一遍單元測試ut,就能保證代碼中沒有產生迴歸錯誤(regressions),而不是去改變ut。

2)單元測試的模式:TDD BDD

(1)TDD: Test-driven Development, 即測試驅動開發

基本思路是經過測試來推進整個開發的進行。原理是在開發功能代碼以前,先編寫單元測試用例代碼,經過測試代碼來肯定須要編寫什麼產品代碼。因此測試驅動開發不只僅是將測試當成驗證的工具,而是把需求分析、設計、質量控制量化的過程。

TDD的測試步驟是:先寫測試——再寫代碼——測試——重構——經過

(2)BDD: Behavior-driven Development, 即行爲驅動開發

BDD是TDD的進化,但關注的課核心是設計,在行爲驅動開發中,定義系統的行爲(由客戶和開發者一塊兒定義系統的行爲,避免表達不一致帶來的問題)是主要工做,而對系統的描述則成了測試標準。

一款BDD模式的測試框架:Jasmine

"Jasmine is a Behavior-Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework.Thus it's suited for websites, Node.js projects, or anywhere that JavaScript can run." —— 來自官網

3)單元測試舉例

以最簡單的運算類函數爲例,通常能覆蓋如下幾種值就好了:

  • 空值
  • 普通合法值
  • 邊界合法值(最大/最小合法值)
  • 邊界非法值(最大/最小非法值)
  • 普通非法值
  • 極限值(最大/最小可能值)
  • 特殊值(若是存在)


3、Test Runner 與 Test Framework

Karma是測試運行器,它只負責找出代碼中全部的單元測試用例,而後打開瀏覽器並測試它們,最終獲取測試結果。並不關心那些測試用例究竟是用什麼語言編寫的,以及咱們究竟採用的是什麼框架,它所作的僅僅是運行這些測試而已。

Jasmine是一種測試框架,它定義了測試用例的語法和API,以及如何爲這些用例編寫斷言。它還有其餘的替代品,如mocha、qunit等。



4、Karma的基本概念(Test Runner)

Karma經過NodeJS和SocketIO來進行測試,適用於不一樣的瀏覽器,速度很快。

1)Karma插件的分類

(1)瀏覽器插件

如Chrome插件karma-chrome-launcher,也有對應的Firefox、IE等瀏覽器的插件。

(2)測試框架

能夠選擇採用哪一種框架來編寫但與測試,如Jasmine的插件karma-jasmine,也有其餘風格的框架的插件,如mocha、qunit。

(3)報表生成

Karma的測試結果提供了豐富的格式,默認的報表生成器是內置的,報表插件如karma-html-reporter,karma-junit-reporter。

(4)集成

它們可以和已有的JS庫或者工具進行整合,Karma插件涵蓋了大部分主流的JS庫。

2)Karma的安裝與配置

(1)在項目根目錄初始化 package.json

npm init

(2)安裝 karma+jasmine 相關包

推薦爲每一個項目本地安裝Karma,而不是安裝一個全局的Karma。

npm install karma -g
npm install karma-jasmine -g
npm install karma-chrome-launcher -g
npm install karma-cli -g
npm install karma-coverage -g
npm install karma-html-reporter -g
npm install karma-junit-reporter -g
(jasmine-core)

(將上面的參數 -g 替換成 --save-dev 會在該項目內安裝,而不是在全局安裝,而且在 package.json 中 dev-dependencies 中顯示依賴的相關包)

(2)配置

karma 提供了自動生成配置文件的方法。執行 karma init,按照提示回答幾個問題便可,默認文件名 karma.config.js。

// 文件 karma.config.js
// Karma configuration
// Generated on Tue Nov 01 2016 14:17:00 GMT+0800 (中國標準時間)

module.exports = function(config) {
    config.set({
        // base path that will be used to resolve all patterns (eg. files, exclude)
        // 放置文件的根目錄
        basePath: '',

        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        // 使用哪些測試框架(jasmine/mocha/qunit/...),如Jasmine,需安裝karma-jasmine插件
        frameworks: ['jasmine'],

        // list of files / patterns to load in the browser
        // 瀏覽器須要加載的文件列表或者文件匹配表達式
        // 須要加載入瀏覽器的js文件,包括基礎的類庫,被測試js源文件和測試用例js文件
        // 若是須要測試angular代碼,好比引入angular-mock.js,給angular代碼進行mock
        files: [
            '../web/vender/jquery/jquery-1.10.2.min.js',
            '../web/vender/angular/angular.min.js',
            '../web/vender/angular/angular-ui-router.min.js',
            '../web/vender/bootstrap_v3.3.5/js/bootstrap.min.js',
            'lib/angular-mocks.js',     // 注意angular-mock的版本必定要和angular版本一致
            '../web/common/*.js',
            /****/
            '../web/bbs/template/*.html',
            '../web/common/template/**/*.html',
            /***/
           'tc/ut/bbs/*.js'
        ],

        // list of files to exclude 須要排除的文件列表或者文件匹配正則表達式
        exclude: [
          // 'tc/ut/apply/apply.js',
           'tc/ut/project/my/task.js',
        ],

        // test results reporter to use 這裏定義輸出的報告
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        // html對應karma-html-reporter組件,coverage對應karma-coverage組件,輸出測試用例執行報告
        reporters: ['progress', 'html', 'junit','coverage'],

        junitReporter: {
          // will be resolved to basePath (in the same way as files/exclude patterns)
          outputFile: 'report/ut/test-results.xml',
          suite: 'UT',
          useBrowserName: false
        },

        htmlReporter: {
          outputDir: 'report/ut',
          reportName: 'result' // outputDir+reportName組成完整的輸出報告格式,如沒有定義,會自動生成瀏覽器+OS信息的文件夾,不方便讀取報告
        },

        //定義須要統計覆蓋率的文件
        preprocessors: {
            '../web/bbs/bbs.js':'coverage',
            '../web/common/*.js':'coverage',
            '../web/bbs/**/*.html': 'ng-html2js',
            '../web/common/template/*.html': 'ng-html2js'
        },

        coverageReporter: {
            type: 'cobertura',  //'cobertura',  //將覆蓋率報告類型type設置爲html
            subdir:'coverage',  //dir+subdir組成完整的輸出報告格式,如沒有定義,會自動生成瀏覽器+OS信息的文件夾,不方便讀取報告
            dir: 'report/ut/'   //代碼覆蓋率報告生成地址
        },

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging 日誌等級
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        // 進行測試時是否容許隨時監視文件變化(被測試文件和測試用用例文件),若有修改,自動從新執行測試
        // 不然用戶就得手動在終端中運行 karma run 進行另外一輪測試,此命令可讓karma以當前的服務器配置再次進行相同的測試
        autoWatch: true,

        // Continuous Integration mode 持續集成模式(是否重複運行)
        // if true, Karma captures browsers, runs the tests and exits
        // 若是設置爲true,則會啓動瀏覽器,運行測試而後退出。在持續集成的環境下,該參數應該被設置爲true
        // 上一個參數爲true,本參數爲false,則自動監視才生效。不然執行完測試用例後自動退出
        singleRun: false,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        // 用來執行自動監聽的瀏覽器,推薦chrome
        browsers: ['Chrome'],

        // Concurrency level
        // how many browser should be started simultaneous
        concurrency: Infinity,

        /*captureTimeout : 60000,
        browserDisconnectTimeout : 10000, // default 2000
        browserDisconnectTolerance : 1, // default 0
        browserNoActivityTimeout : 60000, //default 10000*/

        ngHtml2JsPreprocessor: {
            cacheIdFromPath: function(filepath) {
                var cacheId = filepath.substr(filepath.lastIndexOf('/webapp/')+7);
                // console.log(cacheId);
                return cacheId;
            },
            moduleName: 'template'
        }
    });
};

(3)啓動karma

運行 karma start,會在運行目錄中搜索karma.conf.js文件並加載配置內容。若是配置文件的名稱不是這個名字,或者在不一樣的目錄中,能夠講完整路徑做爲參數傳入:karma start [my.karma.conf.js]

(4)代碼覆蓋率

安裝karma-coverage插件:

npm install karma-coverage --save-dev

修改karma.config.js, 增長覆蓋率配置:

// 定義須要統計覆蓋率的文件
preprocessors: {
    'src/**/*.js': ['coverage']
},
reporters: ['progress', 'html', 'junit', 'coverage'],   //在 reporters 中增長 coverage
coverageReporter: {
    type: 'html',       // 將覆蓋率報告類型type設置爲html  // 'cobertura'
    subdir:'coverage',  // dir+subdir 組成完整的輸出報告格式,如沒有定義,會自動生成瀏覽器+OS信息的文件夾,不方便讀取報告
    dir: 'report/ut/'   // 代碼覆蓋率報告生成地址
}


5、Jasmine的基本概念(Test Framework)

Jasmine框架是一種使用行爲驅動的方式來構建測試的。具體說就是描述想要的行爲並設定預期。
它的大部分的測試代碼demo 參見 【AngularJS的概念及其單元測試】之Jasmine測試腳本Demo


6、參考

AngularJS:Up & Running (AngularJS即學即用)

7、其餘可參考的文章

[1] NG的單元測試

相關文章
相關標籤/搜索