首頁  >  文章  >  web前端  >  關於Angular.Js的自動化測試

關於Angular.Js的自動化測試

不言
不言原創
2018-07-02 16:03:161581瀏覽

這篇文章主要介紹了關於Angular.Js的自動化測試,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

當Angular專案的規模到達一定的程度,就需要進行測試工作了。為什麼要自動化測試? 1,提高產出品質。 2,減少重構時的痛。反正我最近重構多了,痛苦經歷多了。 3,便於新人接手。以下這篇文章就給大家詳細介紹了Angular.Js的自動化測試,有需要的朋友可以參考借鏡。

本文著重介紹關於ng的測試部分,主要包括以下三個面向:

  1. #框架的選擇(Karma Jasmine)

  2. 測試的分類與選擇(單元測試端對端測試)

  3. #ng中各個模組如何寫測試案例

#以下各部分進行詳細介紹。

測試的分類

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

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

下面著重介紹兩種開發模式。

TDD(測試驅動程式開發 Test-driven development)是使用測試案例等來驅動你的軟體開發。

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

  1.      首先,開發人員編寫一些測試方法。

  2.      其次,開發人員使用這些測試,但是很明顯的,測試都沒有通過,原因是還沒有編寫這些功能的程式碼來實際執行。

  3.      接下來,開發人員實作測試中的程式碼。

  4.      如果開發人員寫程式碼很優秀,那麼在下一階段會看到他的測試通過。

  5.      然後開發人員可以重構自己的程式碼,加入註釋,使其變得整潔,開發人員知道,如果新加入的程式碼破壞了什麼,那麼測試會提醒他失敗。

其中的流程圖如下:


#TDD

TDD的好處:

  1.      能驅使系統最終的實作程式碼,都可以被測試程式碼所覆寫到,也也就是「每一行程式碼都可測」。

  2.      測試程式碼作為實現程式碼的正確導向,最後演變為正確系統的行為,能讓整個開發過程更有效率。

BDD是(行為驅動開發 Behavior-Driven Development)指的是不應該針對程式碼的實作細節寫入測試,而是要針對行為寫入測試。 BDD測試的是行為,即軟體應該如何運作。

  1.      和TDD比起來,BDD是需要我們先寫行為規格(功能明細),在進行軟體開發。功能明細和測試看起來非常相似,但是功能明細更含蓄一些。 BDD採用了更詳細的方式使得它看起來像是一句話。

  2.      BDD測試應該專注於功能而不是實際的結果。你常常會聽到BDD是幫助設計軟體,而不是像TDD的測試軟體。

最後總結:TDD的迭代反覆驗證是敏捷開發的保障,但沒有明確如何根據設計產生測試,並保障測試用例的質量,而BDD倡導大家都用簡潔的自然語言描述系統行為的概念,恰好彌補了測試案例(即係統行為)的準確性。

測試框架選擇

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

     Karma:是一個基於Node.js的JavaScript測試執行過程管理工具,這個測試工具的一個強大特性就是,它可以監控(Watch)檔案的變化,然後自行執行,透過console .log顯示測試結果。

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

Karma

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

Karma從頭開始構建,免去了設定測試的負擔,集中精力在應用邏輯上。會產生一個瀏覽器實例,針對不同瀏覽器執行測試,同時可以對測試的運行進行一個即時回饋,提供一份debug報告。

測試也會依賴一些Karma插件,如測試覆蓋率Karma-coverage工具、Karman-fixture工具及Karma-coffee處理工具。此外,前端社群提供裡較豐富的插件,常見的測試需求都能涵蓋。

安裝測試相關的npm模組建議使用—-save-dev參數,因為這是開發相關的,一般的運行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里的基础目录
 basePath : '../',
 // 测试环境需要加载的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'
 ],
 // 是否自动监听上面文件的改变自动运行测试
 autoWatch : true,
 // 应用的测试框架
 frameworks: ['jasmine'],
 // 用什么环境测试代码,这里是chrome`
 browsers : ['Chrome'],
 // 用到的插件,比如chrome浏览器与jasmine插件
 plugins : [
  'karma-chrome-launcher',
  'karma-firefox-launcher',
  'karma-jasmine',
  'karma-junit-reporter'
  ],
 // 测试内容的输出以及导出用的模块名
 reporters: ['progress', 'junit'],
 // 设置输出测试内容文件的信息
 junitReporter : {
 outputFile: 'test_out/unit.xml',
 suite: 'unit'
 }
 });
};

運行時輸入:

karma start test/karma.conf.js

jasmine

jasmine是一個行為驅動開發的測試框架,不依賴任何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函數來定義,它有兩參數,第一個用來描述測試大體的中心內容,第二個參數是一個函數,裡面寫一些真實的測試代碼

  2. #     it是用來定義單一特定測試任務,也有兩個參數,第一個用來描述測試內容,第二個參數是一個函數,裡面存放一些測試方法

  3. #     expect主要用來計算一個變數或一個表達式的值,然後用來跟期望的值比較或做一些其它的事件

  4.      beforeEach與afterEach主要是用來在執行測試任務之前和之後做一些事情,上面的例子就是在執行先前改變變數的值,然後在執行完成之後重置變數的值

#開始單元測試

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

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

先介紹angular中測試相關的元件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中使用,在執行測試案例之前可以獲得模組的配置。

angular.mock.inject: inject用來注入配置好的ng模組,來供測試案例裡進行呼叫。具體使用如下:

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

其實inject裡面就是利用angular.inject方法創建的一個內建的依賴注入實例,然後裡面的模組和普通的ng模組的依賴處理是一樣的。

Controller部分

Angular模組是todoApp,控制器是TodoController,當按鈕被點擊時,TodoController的createNote() 函數會被呼叫。下面是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()被呼叫時,會使用這個服務將一則訊息存入LocalStorage中,然後清空目前的note。因此,在編寫測試模組是,應該保證控制器初始化,scope中有一定數量的筆記,在調用createNote()之後,筆記的數量應該加一。

具體的單元測試如下:

describe(&#39;TodoController Test&#39;, function() {
 beforeEach(module(&#39;todoApp&#39;)); // 将会在所有的it()之前运行
 // 我们在这里不需要真正的factory。因此我们使用一个假的factory。
 var mockService = {
 notes: [&#39;note1&#39;, &#39;note2&#39;], //仅仅初始化两个项目
 get: function() {
 return this.notes;
 },
 put: function(content) {
 this.notes.push(content);
 }
 };
 // 现在是真正的东西,测试spec
 it(&#39;should return notes array with two elements initially and then add one&#39;,
 inject(function($rootScope, $controller) { //注入依赖项目
 var scope = $rootScope.$new();
 // 在创建控制器的时候,我们也要注入依赖项目
 var ctrl = $controller(&#39;TodoController&#39;, {$scope: scope, notesFactory:mockService});
 // 初始化的技术应该是2
 expect(scope.notes.length).toBe(2);
 // 输入一个新项目
 scope.note = &#39;test3&#39;;
 // now run the function that adds a new note (the result of hitting the button in HTML)
 // 现在运行这个函数,它将会增加一个新的笔记项目
 scope.createNote();
 // 期待现在的笔记数目是3
 expect(scope.notes.length).toBe(3);
 })
 );
});

在beforeEach中,每一個測試案例被執行之前,都需要載入模組module("todoApp")

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

在it中,宣告了依賴項目$rootScope$controller,都可以由Angular自動注入,其中$rootScope用來取得根作用域,$controller用作建立新的控制器。

$controller服务需要两个参数。第一个参数是将要创建的控制器的名称。第二个参数是一个代表控制器依赖项目的对象,
$rootScope.$new()方法将会返回一个新的作用域,它用来注入控制器。同时我们传入mockService作为假factory。
之后,初始化会根据notes数组的长度预测笔记的数量,同时在执行了createNote()函数之后,会改变数组的长度,因此可以写出两个测试用例。

Factory部分

factory部分的单元测试代码如下:

describe(&#39;notesFactory tests&#39;, function() {
 var factory;
 // 在所有it()函数之前运行
 beforeEach(function() {
 // 载入模块
 module(&#39;todoApp&#39;);
 // 注入你的factory服务
 inject(function(notesFactory) {
 factory = notesFactory;
 });
 var store = {
 todo1: &#39;test1&#39;,
 todo2: &#39;test2&#39;,
 todo3: &#39;test3&#39;
 };
 spyOn(localStorage, &#39;getItem&#39;).andCallFake(function(key) {
 return store[key];
 });
 spyOn(localStorage, &#39;setItem&#39;).andCallFake(function(key, value) {
 return store[key] = value + &#39;&#39;;
 });
 spyOn(localStorage, &#39;clear&#39;).andCallFake(function() {
 store = {};
 });
 spyOn(Object, &#39;keys&#39;).andCallFake(function(value) {
 var keys=[];
 for(var key in store) {
 keys.push(key);
 }
 return keys;
 });
 });
 // 检查是否有我们想要的函数
 it(&#39;should have a get function&#39;, function() {
 expect(angular.isFunction(factory.get)).toBe(true);
 expect(angular.isFunction(factory.put)).toBe(true);
 });
 // 检查是否返回3条记录
 it(&#39;should return three todo notes initially&#39;, function() {
 var result = factory.get();
 expect(result.length).toBe(3);
 });
 // 检查是否添加了一条新纪录
 it(&#39;should return four todo notes after adding one more&#39;, function() {
 factory.put(&#39;Angular is awesome&#39;);
 var result = factory.get();
 expect(result.length).toBe(4);
 });
});

在TodoController模块中,实际上的factory会调用localStorage来存储和提取笔记的项目,但由于我们单元测试中,不需要依赖外部服务去获取和存储数据,因此我们要对localStorage.getItem()localStorage.setItem()进行spy操作,也就是利用假函数来代替这两个部分。

spyOn(localStorage,'setItem')andCallFake()是用来用假函数进行监听的。第一个参数指定需要监听的对象,第二个参数指定需要监听的函数,然后andCallfake这个API可以编写自己的函数。因此,测试中完成了对localStorage和Object的改写,使函数可以返回我们自己数组中的值。

在测试用例中,首先检测新封装的factory函数是否包含了get()put()这两个方法,,然后进行factory.put()操作后断言笔记的数量。

Filter部分

我们添加一个过滤器。truncate的作用是如果传入字符串过长后截取前10位。源码如下:

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

所以在单元测试中,可以根据传入字符串的情况断言生成子串的长度。

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

之前已经对断言进行讨论了,值得注意的一点是我们需要在调用过滤器的时候在名称后面加入Filter,然后正常调用即可。

Directive部分

源码中的指令部分:

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

由于指令必须编译之后才能生成相关的模板,因此我们要引入$compile服务来完成实际的编译,然后再测试我们想要进行测试的元素。

angular.element()会创建一个jqLite元素,然后我们将其编译到一个新生成的自作用域中,就可以被测试了。具体测试用例如下:

describe(&#39;directive tests&#39;,function(){
 beforeEach(module(&#39;todoApp&#39;));
 it(&#39;should set background to rgb(128, 128, 128)&#39;,
 inject(function($compile,$rootScope) {
 scope = $rootScope.$new();
 // 获得一个元素
 elem = angular.element("<span custom-color=\"rgb(128, 128, 128)\">sample</span>");
 // 创建一个新的自作用域
 scope = $rootScope.$new();
 // 最后编译HTML
 $compile(elem)(scope);
 // 希望元素的背景色和我们所想的一样
 expect(elem.css("background-color")).toEqual(&#39;rgb(128, 128, 128)&#39;);
 })
 );
});

开始端到端测试

在端到端测试中,我们需要从用户的角度出发,来进行黑盒测试,因此会涉及到一些DOM操作。将一对组件组合起来然后检查是否如预想的结果一样。
在这个demo中,我们模拟用户输入信息并按下按钮的过程,检测信息能否被添加到localStorage中。

在E2E测试中,需要引入angular-scenario这个文件,并且建立一个html作为运行report的展示,在html中包含带有e2e测试代码的执行js文件,在编写完测试之后,运行该html文件查看结果。具体的e2e代码如下:

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

我们在端到端测试过程中,首先导航到我们的主html页面app/notes.html,可以通过browser.navigateTo()来完成,element.query()函数选择了ul元素并记录其中有多少个初始化的项目,存放在oldCount变量中。

然后通过input('note').enter()来键入一个新的笔记,然后模拟一下点击操作来检查是否增加了一个新的笔记(li元素)。然后通过断言可以将新旧的笔记数进行对比。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

关于AJax与Jsonp跨域访问的问题

三种AngularJS中获取数据源的方式

以上是關於Angular.Js的自動化測試的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn