欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Angular.Js的自動化測試詳解

 更新時間:2016年12月09日 09:55:29   投稿:daisy  
當Angular項目的規(guī)模到達一定的程度,就需要進行測試工作了。為什么要自動化測試?1,提高產(chǎn)出質(zhì)量。2,減少重構(gòu)時的痛。反正我最近重構(gòu)多了,痛苦經(jīng)歷多了。3,便于新人接手。下面這篇文章就給大家詳細介紹了Angular.Js的自動化測試,有需要的朋友們可以參考借鑒。

本文著重介紹關(guān)于ng的測試部分,主要包括以下三個方面:

  1. 框架的選擇(Karma+Jasmine)
  2. 測試的分類和選擇(單元測試 + 端到端測試)
  3. 在ng中各個模塊如何編寫測試用例

下面各部分進行詳細介紹。

測試的分類

在測試中,一般分為單元測試和端到端測試,單元測試是保證開發(fā)者驗證代碼某部分有效性的技術(shù),端到端(E2E)是當你想確保一堆組件能按事先預想的方式運行起來的時候使用。

其中單元測試又分為兩類: TDD(測試驅(qū)動開發(fā))和BDD(行為驅(qū)動開發(fā))。

下面著重介紹兩種開發(fā)模式。

TDD(測試驅(qū)動開發(fā) Test-driven development)是使用測試案例等來驅(qū)動你的軟件開發(fā)。

如果我們想要更深入點了解TDD,我們可以將它分成五個不同的階段:

  1.      首先,開發(fā)人員編寫一些測試方法。
  2.      其次,開發(fā)人員使用這些測試,但是很明顯的,測試都沒有通過,原因是還沒有編寫這些功能的代碼來實際執(zhí)行。
  3.      接下來,開發(fā)人員實現(xiàn)測試中的代碼。
  4.      如果開發(fā)人員寫代碼很優(yōu)秀,那么在下一階段會看到他的測試通過。
  5.      然后開發(fā)人員可以重構(gòu)自己的代碼,添加注釋,使其變得整潔,開發(fā)人員知道,如果新添加的代碼破壞了什么,那么測試會提醒他失敗。

其中的流程圖如下:


TDD

TDD的好處:

  1.      能驅(qū)使系統(tǒng)最終的實現(xiàn)代碼,都可以被測試代碼所覆蓋到,也即“每一行代碼都可測”。
  2.      測試代碼作為實現(xiàn)代碼的正確導向,最終演變?yōu)檎_系統(tǒng)的行為,能讓整個開發(fā)過程更加高效。

BDD是(行為驅(qū)動開發(fā) Behavior-Driven Development)指的是不應該針對代碼的實現(xiàn)細節(jié)寫測試,而是要針對行為寫測試。BDD測試的是行為,即軟件應該怎樣運行。

  1.      和TDD比起來,BDD是需要我們先寫行為規(guī)范(功能明細),在進行軟件開發(fā)。功能明細和測試看起來非常相似,但是功能明細更加含蓄一些。BDD采用了更詳細的方式使得它看起來就像是一句話。
  2.      BDD測試應該注重功能而不是實際的結(jié)果。你常常會聽說BDD是幫助設(shè)計軟件,而不是像TDD那樣的測試軟件。

最后總結(jié):TDD的迭代反復驗證是敏捷開發(fā)的保障,但沒有明確如何根據(jù)設(shè)計產(chǎn)生測試,并保障測試用例的質(zhì)量,而BDD倡導大家都用簡潔的自然語言描述系統(tǒng)行為的理念,恰好彌補了測試用例(即系統(tǒng)行為)的準確性。

測試框架選擇

利用karma和jasmine來進行ng模塊的單元測試。

     Karma:是一個基于Node.js的JavaScript測試執(zhí)行過程管理工具,這個測試工具的一個強大特性就是,它可以監(jiān)控(Watch)文件的變化,然后自行執(zhí)行,通過console.log顯示測試結(jié)果。

     jasmine是一個行為驅(qū)動開發(fā)(BDD)的測試框架,不依賴任何js框架以及dom,是一個非常干凈以及友好API的測試庫.

Karma

karma是一個單元測試的運行控制框架,提供以不同環(huán)境來運行單元測試,比如chrome,firfox,phantomjs等,測試框架支持jasmine,mocha,qunit,是一個以nodejs為環(huán)境的npm模塊.

Karma從頭開始構(gòu)建,免去了設(shè)置測試的負擔,集中精力在應用邏輯上。會產(chǎn)生一個瀏覽器實例,針對不同瀏覽器運行測試,同時可以對測試的運行進行一個實時反饋,提供一份debug報告。

測試還會依賴一些Karma插件,如測試覆蓋率Karma-coverage工具、Karman-fixture工具及Karma-coffee處理工具。此外,前端社區(qū)里提供里比較豐富的插件,常見的測試需求都能涵蓋到。

安裝測試相關(guān)的npm模塊建議使用—-save-dev參數(shù),因為這是開發(fā)相關(guān)的,一般的運行karma的話只需要下面兩個npm命令:

npm install karma --save-dev
npm install karma-junit-reporter --save-dev

然后一個典型的運行框架通常都需要一個配置文件,在karma里可以是一個karma.conf.js,里面的代碼是一個nodejs風格的,一個普通的例子如下:

module.exports = function(config){
 config.set({
 // 下面files里的基礎(chǔ)目錄
 basePath : '../',
 // 測試環(huán)境需要加載的JS信息
 files : [
 'app/bower_components/angular/angular.js',
 'app/bower_components/angular-route/angular-route.js',
 'app/bower_components/angular-mocks/angular-mocks.js',
 'app/js/**/*.js',
 'test/unit/**/*.js'
 ],
 // 是否自動監(jiān)聽上面文件的改變自動運行測試
 autoWatch : true,
 // 應用的測試框架
 frameworks: ['jasmine'],
 // 用什么環(huán)境測試代碼,這里是chrome`
 browsers : ['Chrome'],
 // 用到的插件,比如chrome瀏覽器與jasmine插件
 plugins : [
  'karma-chrome-launcher',
  'karma-firefox-launcher',
  'karma-jasmine',
  'karma-junit-reporter'
  ],
 // 測試內(nèi)容的輸出以及導出用的模塊名
 reporters: ['progress', 'junit'],
 // 設(shè)置輸出測試內(nèi)容文件的信息
 junitReporter : {
 outputFile: 'test_out/unit.xml',
 suite: 'unit'
 }
 });
};

運行時輸入:

karma start test/karma.conf.js

jasmine

jasmine是一個行為驅(qū)動開發(fā)的測試框架,不依賴任何js框架以及dom,是一個非常干凈以及友好API的測試庫.

以下以一個具體實例說明test.js:

describe("A spec (with setup and tear-down)", function() {
 var foo;
 beforeEach(function() {
 foo = 0;
 foo += 1;
 });
 afterEach(function() {
 foo = 0;
 });
 it("is just a function, so it can contain any code", function() {
 expect(foo).toEqual(1);
 });
 it("can have more than one expectation", function() {
 expect(foo).toEqual(1);
 expect(true).toEqual(true);
 });
});
  1.      首先任何一個測試用例以describe函數(shù)來定義,它有兩參數(shù),第一個用來描述測試大體的中心內(nèi)容,第二個參數(shù)是一個函數(shù),里面寫一些真實的測試代碼
  2.      it是用來定義單個具體測試任務,也有兩個參數(shù),第一個用來描述測試內(nèi)容,第二個參數(shù)是一個函數(shù),里面存放一些測試方法
  3.      expect主要用來計算一個變量或者一個表達式的值,然后用來跟期望的值比較或者做一些其它的事件
  4.      beforeEach與afterEach主要是用來在執(zhí)行測試任務之前和之后做一些事情,上面的例子就是在執(zhí)行之前改變變量的值,然后在執(zhí)行完成之后重置變量的值

開始單元測試

下面分別以控制器,指令,過濾器和服務四個部分來編寫相關(guān)的單元測試。項目地址為angular-seed(點我)項目,可以下載demo并運行其測試用例。

demo中是一個簡單的todo應用,會包含一個文本輸入框,其中可以編寫一些筆記,按下按鈕可以將新的筆記加入筆記列表中,其中使用notesfactory封裝LocalStorage來儲存筆記信息。

先介紹一下angular中測試相關(guān)的組件angular-mocks。

了解angular-mocks

在Angular中,模塊都是通過依賴注入來加載和實例化的,因此官方提供了angular-mocks.js測試工具來提供模塊的定義、加載,依賴注入等功能。

其中一些常用的方法(掛載在window命名空間下):

angular.mock.module: module用來加載已有的模塊,以及配置inject方法注入的模塊信息。具體使用如下:

beforeEach(module('myApp.filters'));
beforeEach(module(function($provide) {
 $provide.value('version', 'TEST_VER');
}));

該方法一般在beforeEach中使用,在執(zhí)行測試用例之前可以獲得模塊的配置。

angular.mock.inject: inject用來注入配置好的ng模塊,來供測試用例里進行調(diào)用。具體使用如下:

it('should provide a version', inject(function(mode, version) {
 expect(version).toEqual('v1.0.1');
 expect(mode).toEqual('app');
 }));

其實inject里面就是利用angular.inject方法創(chuàng)建的一個內(nèi)置的依賴注入實例,然后里面的模塊和普通的ng模塊的依賴處理是一樣的。

Controller部分

Angular模塊是todoApp,控制器是TodoController,當按鈕被點擊時,TodoController的createNote()函數(shù)會被調(diào)用。下面是app.js的代碼部分。

var todoApp = angular.module('todoApp',[]);
todoApp.controller('TodoController',function($scope,notesFactory){
 $scope.notes = notesFactory.get();
 $scope.createNote = function(){
 notesFactory.put($scope.note);
 $scope.note='';
 $scope.notes = notesFactory.get();
 }
});
todoApp.factory('notesFactory',function(){
 return {
 put: function(note){ 
 localStorage.setItem('todo' + (Object.keys(localStorage).length + 1), note);
 },
 get: function(){
 var notes = [];
 var keys = Object.keys(localStorage);
 for(var i = 0; i < keys.length; i++){
  notes.push(localStorage.getItem(keys[i]));
 }
 return notes;
 } 
 };
});

在todoController中用了個叫做notesFactory的服務來存儲和提取筆記。當createNote()被調(diào)用時,會使用這個服務將一條信息存入LocalStorage中,然后清空當前的note。因此,在編寫測試模塊是,應該保證控制器初始化,scope中有一定數(shù)量的筆記,在調(diào)用createNote()之后,筆記的數(shù)量應該加一。

具體的單元測試如下:

describe('TodoController Test', function() {
 beforeEach(module('todoApp')); // 將會在所有的it()之前運行
 // 我們在這里不需要真正的factory。因此我們使用一個假的factory。
 var mockService = {
 notes: ['note1', 'note2'], //僅僅初始化兩個項目
 get: function() {
 return this.notes;
 },
 put: function(content) {
 this.notes.push(content);
 }
 };
 // 現(xiàn)在是真正的東西,測試spec
 it('should return notes array with two elements initially and then add one',
 inject(function($rootScope, $controller) { //注入依賴項目
 var scope = $rootScope.$new();
 // 在創(chuàng)建控制器的時候,我們也要注入依賴項目
 var ctrl = $controller('TodoController', {$scope: scope, notesFactory:mockService});
 // 初始化的技術(shù)應該是2
 expect(scope.notes.length).toBe(2);
 // 輸入一個新項目
 scope.note = 'test3';
 // now run the function that adds a new note (the result of hitting the button in HTML)
 // 現(xiàn)在運行這個函數(shù),它將會增加一個新的筆記項目
 scope.createNote();
 // 期待現(xiàn)在的筆記數(shù)目是3
 expect(scope.notes.length).toBe(3);
 })
 );
});

在beforeEach中,每一個測試用例被執(zhí)行之前,都需要加載模塊module("todoApp")

由于不需要外部以來,因此我們本地建立一個假的mockService來代替factory,用來模擬noteFactory,其中包含相同的函數(shù),get()put() 。這個假的factory從數(shù)組中加載數(shù)據(jù)代替localStorage的操作。

在it中,聲明了依賴項目$rootScope$controller,都可以由Angular自動注入,其中$rootScope用來獲得根作用域,$controller用作創(chuàng)建新的控制器。

$controller服務需要兩個參數(shù)。第一個參數(shù)是將要創(chuàng)建的控制器的名稱。第二個參數(shù)是一個代表控制器依賴項目的對象,
$rootScope.$new()方法將會返回一個新的作用域,它用來注入控制器。同時我們傳入mockService作為假factory。
之后,初始化會根據(jù)notes數(shù)組的長度預測筆記的數(shù)量,同時在執(zhí)行了createNote()函數(shù)之后,會改變數(shù)組的長度,因此可以寫出兩個測試用例。

Factory部分

factory部分的單元測試代碼如下:

describe('notesFactory tests', function() {
 var factory;
 // 在所有it()函數(shù)之前運行
 beforeEach(function() {
 // 載入模塊
 module('todoApp');
 // 注入你的factory服務
 inject(function(notesFactory) {
 factory = notesFactory;
 });
 var store = {
 todo1: 'test1',
 todo2: 'test2',
 todo3: 'test3'
 };
 spyOn(localStorage, 'getItem').andCallFake(function(key) {
 return store[key];
 });
 spyOn(localStorage, 'setItem').andCallFake(function(key, value) {
 return store[key] = value + '';
 });
 spyOn(localStorage, 'clear').andCallFake(function() {
 store = {};
 });
 spyOn(Object, 'keys').andCallFake(function(value) {
 var keys=[];
 for(var key in store) {
 keys.push(key);
 }
 return keys;
 });
 });
 // 檢查是否有我們想要的函數(shù)
 it('should have a get function', function() {
 expect(angular.isFunction(factory.get)).toBe(true);
 expect(angular.isFunction(factory.put)).toBe(true);
 });
 // 檢查是否返回3條記錄
 it('should return three todo notes initially', function() {
 var result = factory.get();
 expect(result.length).toBe(3);
 });
 // 檢查是否添加了一條新紀錄
 it('should return four todo notes after adding one more', function() {
 factory.put('Angular is awesome');
 var result = factory.get();
 expect(result.length).toBe(4);
 });
});

在TodoController模塊中,實際上的factory會調(diào)用localStorage來存儲和提取筆記的項目,但由于我們單元測試中,不需要依賴外部服務去獲取和存儲數(shù)據(jù),因此我們要對localStorage.getItem()localStorage.setItem()進行spy操作,也就是利用假函數(shù)來代替這兩個部分。

spyOn(localStorage,'setItem')andCallFake()是用來用假函數(shù)進行監(jiān)聽的。第一個參數(shù)指定需要監(jiān)聽的對象,第二個參數(shù)指定需要監(jiān)聽的函數(shù),然后andCallfake這個API可以編寫自己的函數(shù)。因此,測試中完成了對localStorage和Object的改寫,使函數(shù)可以返回我們自己數(shù)組中的值。

在測試用例中,首先檢測新封裝的factory函數(shù)是否包含了get()put()這兩個方法,,然后進行factory.put()操作后斷言筆記的數(shù)量。

Filter部分

我們添加一個過濾器。truncate的作用是如果傳入字符串過長后截取前10位。源碼如下:

todoApp.filter('truncate',function(){
 return function(input,length){
 return (input.length > length ? input.substring(0,length) : input);
 }
});

所以在單元測試中,可以根據(jù)傳入字符串的情況斷言生成子串的長度。

describe('filter test',function(){
 beforeEach(module('todoApp'));
 it('should truncate the input to 1o characters',inject(function(truncateFilter){
 expect(truncateFilter('abcdefghijkl',10).length).toBe(10);
 });
 );
});

之前已經(jīng)對斷言進行討論了,值得注意的一點是我們需要在調(diào)用過濾器的時候在名稱后面加入Filter,然后正常調(diào)用即可。

Directive部分

源碼中的指令部分:

todoApp.directive('customColor', function() {
 return {
 restrict: 'A',
 link: function(scope, elem, attrs) {
 elem.css({'background-color': attrs.customColor});
 }
 };
});

由于指令必須編譯之后才能生成相關(guān)的模板,因此我們要引入$compile服務來完成實際的編譯,然后再測試我們想要進行測試的元素。

angular.element()會創(chuàng)建一個jqLite元素,然后我們將其編譯到一個新生成的自作用域中,就可以被測試了。具體測試用例如下:

describe('directive tests',function(){
 beforeEach(module('todoApp'));
 it('should set background to rgb(128, 128, 128)',
 inject(function($compile,$rootScope) {
 scope = $rootScope.$new();
 // 獲得一個元素
 elem = angular.element("<span custom-color=\"rgb(128, 128, 128)\">sample</span>");
 // 創(chuàng)建一個新的自作用域
 scope = $rootScope.$new();
 // 最后編譯HTML
 $compile(elem)(scope);
 // 希望元素的背景色和我們所想的一樣
 expect(elem.css("background-color")).toEqual('rgb(128, 128, 128)');
 })
 );
});

開始端到端測試

在端到端測試中,我們需要從用戶的角度出發(fā),來進行黑盒測試,因此會涉及到一些DOM操作。將一對組件組合起來然后檢查是否如預想的結(jié)果一樣。
在這個demo中,我們模擬用戶輸入信息并按下按鈕的過程,檢測信息能否被添加到localStorage中。

在E2E測試中,需要引入angular-scenario這個文件,并且建立一個html作為運行report的展示,在html中包含帶有e2e測試代碼的執(zhí)行js文件,在編寫完測試之后,運行該html文件查看結(jié)果。具體的e2e代碼如下:

describe('my app', function() {
 beforeEach(function() {
 browser().navigateTo('../../app/notes.html');
 });
 var oldCount = -1;
 it("entering note and performing click", function() {
 element('ul').query(function($el, done) {
 oldCount = $el.children().length;
 done();
 });
 input('note').enter('test data');
 element('button').query(function($el, done) {
 $el.click();
 done();
 });
 });
 it('should add one more element now', function() {
 expect(repeater('ul li').count()).toBe(oldCount + 1);
 }); 
});

我們在端到端測試過程中,首先導航到我們的主html頁面app/notes.html,可以通過browser.navigateTo()來完成,element.query()函數(shù)選擇了ul元素并記錄其中有多少個初始化的項目,存放在oldCount變量中。

然后通過input('note').enter()來鍵入一個新的筆記,然后模擬一下點擊操作來檢查是否增加了一個新的筆記(li元素)。然后通過斷言可以將新舊的筆記數(shù)進行對比。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

最新評論