當(dāng)ng
項(xiàng)目越來越大的時(shí)候,單元測(cè)試就要提上日程了,有的時(shí)候團(tuán)隊(duì)是以測(cè)試先行,有的是先實(shí)現(xiàn)功能,后面再測(cè)試功能模塊,這個(gè)各有利弊,今天主要說說利用karma
和jasmine
來進(jìn)行ng
模塊的單元測(cè)試.
karma
是一個(gè)單元測(cè)試的運(yùn)行控制框架,提供以不同環(huán)境來運(yùn)行單元測(cè)試,比如chrome
,firfox
,phantomjs
等,測(cè)試框架支持jasmine
,mocha
,qunit
,是一個(gè)以nodejs
為環(huán)境的npm
模塊.
安裝測(cè)試相關(guān)的npm
模塊建議使用----save-dev
參數(shù),因?yàn)檫@是開發(fā)相關(guān)的,一般的運(yùn)行karma
的話只需要下面兩個(gè)npm
命令
安裝karma
的時(shí)候會(huì)自動(dòng)的安裝一些常用的模塊,參考karma
代碼里的package.json
文件的peerDependencies
屬性
"peerDependencies": { "karma-jasmine": "~0.1.0", "karma-requirejs": "~0.2.0", "karma-coffee-preprocessor": "~0.1.0", "karma-html2js-preprocessor": "~0.1.0", "karma-chrome-launcher": "~0.1.0", "karma-firefox-launcher": "~0.1.0", "karma-phantomjs-launcher": "~0.1.0", "karma-script-launcher": "~0.1.0" }
然后一個(gè)典型的運(yùn)行框架通常都需要一個(gè)配置文件,在karma
里可以是一個(gè)karma.conf.js
,里面的代碼是一個(gè)nodejs
風(fēng)格的,一個(gè)普通的例子如下
module.exports = function(config){ config.set({ // 下面files里的基礎(chǔ)目錄 basePath : '../', // 測(cè)試環(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' ], // 是否自動(dòng)監(jiān)聽上面文件的改變自動(dòng)運(yùn)行測(cè)試 autoWatch : true, // 應(yīng)用的測(cè)試框架 frameworks: ['jasmine'], // 用什么環(huán)境測(cè)試代碼,這里是chrome` browsers : ['Chrome'], // 用到的插件,比如chrome瀏覽器與jasmine插件 plugins : [ 'karma-chrome-launcher', 'karma-firefox-launcher', 'karma-jasmine', 'karma-junit-reporter' ], // 測(cè)試內(nèi)容的輸出以及導(dǎo)出用的模塊名 reporters: ['progress', 'junit'], // 設(shè)置輸出測(cè)試內(nèi)容文件的信息 junitReporter : { outputFile: 'test_out/unit.xml', suite: 'unit' } });};
這里要注意的時(shí),上面的插件大部分都不需要單獨(dú)安裝,因?yàn)榘惭bkarma
的時(shí)候已經(jīng)安裝了,這里只有karma-junit-reporter
導(dǎo)出插件需要單獨(dú)安裝,想要了解更多的關(guān)于配置文件的信息可以,點(diǎn)擊這里
karma
就講到這里,想了解更多關(guān)于它的信息可以,點(diǎn)擊這里
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
上面是jasmine
官方文檔里對(duì)它的解釋,下面用中文簡(jiǎn)單的翻譯下
jasmine是一個(gè)行為驅(qū)動(dòng)開發(fā)的測(cè)試框架,不依賴任何
js
框架以及dom
,是一個(gè)非常干凈以及友好API的測(cè)試庫(kù).
下面簡(jiǎn)單的以一個(gè)例子來說明它的用法
定義一個(gè)測(cè)試文件命令為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); });});
上面的例子來自于官網(wǎng),這里只說下幾個(gè)重要的API,更多的用法請(qǐng),點(diǎn)擊這里
首先任何一個(gè)測(cè)試用例以describe
函數(shù)來定義,它有兩參數(shù),第一個(gè)用來描述測(cè)試大體的中心內(nèi)容,第二個(gè)參數(shù)是一個(gè)函數(shù),里面寫一些真實(shí)的測(cè)試代碼
it
是用來定義單個(gè)具體測(cè)試任務(wù),也有兩個(gè)參數(shù),第一個(gè)用來描述測(cè)試內(nèi)容,第二個(gè)參數(shù)是一個(gè)函數(shù),里面存放一些測(cè)試方法
expect
主要用來計(jì)算一個(gè)變量或者一個(gè)表達(dá)式的值,然后用來跟期望的值比較或者做一些其它的事件
beforeEach
與afterEach
主要是用來在執(zhí)行測(cè)試任務(wù)之前和之后做一些事情,上面的例子就是在執(zhí)行之前改變變量的值,然后在執(zhí)行完成之后重置變量的值
最后要說的是,describe
函數(shù)里的作用域跟普通JS一樣都是可以在里面的子函數(shù)里訪問的,就像上面的it
訪問foo
變量
想要運(yùn)行上面的測(cè)試?yán)涌梢酝ㄟ^karar
來運(yùn)行,命令例子如下
karma start test/karma.conf.js
下面我們重點(diǎn)的說說ng
里的控制器,指令,服務(wù)模塊的單元測(cè)試.
因?yàn)?code>ng本身框架的原因,模塊都是通過di
來加載以及實(shí)例化的,所以為了方便配合jasmine
來編寫測(cè)試腳本,所以官方提供了angular-mock.js
的一個(gè)測(cè)試工具類來提供模塊定義,加載,注入等.
下面說說ng-mock
里的一些常用方法
module
是用來配置inject
方法注入的模塊信息,參數(shù)可以是字符串,函數(shù),對(duì)象,可以像下面這樣使用
beforeEach(module('myApp.filters'));beforeEach(module(function($provide) { $provide.value('version', 'TEST_VER');}));
它一般用在beforeEach
方法里,因?yàn)檫@個(gè)可以確保在執(zhí)行測(cè)試任務(wù)的時(shí)候,inject
方法可以獲取到模塊配置
inject
是用來注入上面配置好的ng
模塊,方面在it
的測(cè)試函數(shù)里調(diào)用,常見的調(diào)用例子如下
angular.module('myApplicationModule', []) .value('mode', 'app') .value('version', 'v1.0.1'); describe('MyApp', function() { // You need to load modules that you want to test, // it loads only the "ng" module by default. beforeEach(module('myApplicationModule')); // inject() is used to inject arguments of all given functions it('should provide a version', inject(function(mode, version) { expect(version).toEqual('v1.0.1'); expect(mode).toEqual('app'); })); // The inject and module method can also be used inside of the it or beforeEach it('should override a version and test the new version is injected', function() { // module() takes functions or strings (module aliases) module(function($provide) { $provide.value('version', 'overridden'); // override version here }); inject(function(version) { expect(version).toEqual('overridden'); }); }); });
上面是官方提供的一些inject
例子,代碼很好看懂,其實(shí)inject
里面就是利用angular.inject
方法創(chuàng)建的一個(gè)內(nèi)置的依賴注入實(shí)例,然后里面的模塊注入跟普通ng
模塊里的依賴處理是一樣的
簡(jiǎn)單的介紹完ng-mock
之后,下面我們分別以控制器,指令,過濾器來編寫一個(gè)簡(jiǎn)單的單元測(cè)試.
ng里控制器的單元測(cè)試
定義一個(gè)簡(jiǎn)單的控制器
var myApp = angular.module('myApp',[]); myApp.controller('MyController', function($scope) { $scope.spices = [{"name":"pasilla", "spiciness":"mild"}, {"name":"jalapeno", "spiciness":"hot hot hot!"}, {"name":"habanero", "spiciness":"LAVA HOT!!"}]; $scope.spice = "hello feenan!";});
然后我們編寫一個(gè)測(cè)試腳本
describe('myController function', function() { describe('myController', function() { var $scope; beforeEach(module('myApp')); beforeEach(inject(function($rootScope, $controller) { $scope = $rootScope.$new(); $controller('MyController', {$scope: $scope}); })); it('should create "spices" model with 3 spices', function() { expect($scope.spices.length).toBe(3); }); it('should set the default value of spice', function() { expect($scope.spice).toBe('hello feenan!'); }); });});
上面利用了$rootScope
來創(chuàng)建子作用域,然后把這個(gè)參數(shù)傳進(jìn)控制器的構(gòu)建方法$controller
里去,最終會(huì)執(zhí)行上面的控制器里的方法,然后我們檢查子作用域里的數(shù)組數(shù)量以及字符串變量是否跟期望的值相等.
想要了解更多關(guān)于ng
里的控制器的信息,可以點(diǎn)擊這里
ng里指令的單元測(cè)試
定義一個(gè)簡(jiǎn)單的指令
var app = angular.module('myApp', []);app.directive('aGreatEye', function () { return { restrict: 'E', replace: true, template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>' };});
然后我們編寫一個(gè)簡(jiǎn)單的測(cè)試腳本
describe('Unit testing great quotes', function() { var $compile; var $rootScope; // Load the myApp module, which contains the directive beforeEach(module('myApp')); // Store references to $rootScope and $compile // so they are available to all tests in this describe block beforeEach(inject(function(_$compile_, _$rootScope_){ // The injector unwraps the underscores (_) from around the parameter names when matching $compile = _$compile_; $rootScope = _$rootScope_; })); it('Replaces the element with the appropriate content', function() { // Compile a piece of HTML containing the directive var element = $compile("<a-great-eye></a-great-eye>")($rootScope); // fire all the watches, so the scope expression {{1 + 1}} will be evaluated $rootScope.$digest(); // Check that the compiled element contains the templated content expect(element.html()).toContain("lidless, wreathed in flame, 2 times"); });});
上面的例子來自于官方提供的,最終上面的指令將會(huì)這用在html
里使用
<a-great-eye></a-great-eye>
測(cè)試腳本里首先注入$compile
與$rootScope
兩個(gè)服務(wù),一個(gè)用來編譯html
,一個(gè)用來創(chuàng)建作用域用,注意這里的_
,默認(rèn)ng
里注入的服務(wù)前后加上_
時(shí),最后會(huì)被ng
處理掉的,這兩個(gè)服務(wù)保存在內(nèi)部的兩個(gè)變量里,方便下面的測(cè)試用例能調(diào)用到
$compile
方法傳入原指令html
,然后在返回的函數(shù)里傳入$rootScope
,這樣就完成了作用域與視圖的綁定,最后調(diào)用$rootScope.$digest
來觸發(fā)所有監(jiān)聽,保證視圖里的模型內(nèi)容得到更新
然后獲取當(dāng)前指令對(duì)應(yīng)元素的html
內(nèi)容與期望值進(jìn)行對(duì)比.
想要了解更多關(guān)于ng
里的指令的信息,可以點(diǎn)擊這里
ng里的過濾器單元測(cè)試
定義一個(gè)簡(jiǎn)單的過濾器
var app = angular.module('myApp', []);app.filter('interpolate', ['version', function(version) { return function(text) { return String(text).replace(/\%VERSION\%/mg, version); }; }]);
然后編寫一個(gè)簡(jiǎn)單的測(cè)試腳本
describe('filter', function() { beforeEach(module('myApp')); describe('interpolate', function() { beforeEach(module(function($provide) { $provide.value('version', 'TEST_VER'); })); it('should replace VERSION', inject(function(interpolateFilter) { expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); })); });});
上面的代碼先配置過濾器模塊,然后定義一個(gè)version
值,因?yàn)?code>interpolate依賴這個(gè)服務(wù),最后用inject
注入interpolate
過濾器,注意這里的過濾器后面得加上Filter
后綴,最后傳入文本內(nèi)容到過濾器函數(shù)里執(zhí)行,與期望值進(jìn)行對(duì)比.
利用測(cè)試來開發(fā)NG有很多好處,可以保證模塊的穩(wěn)定性,還有一點(diǎn)就是能夠深入的了解ng的內(nèi)部運(yùn)行機(jī)制,所以建議用ng開發(fā)的同學(xué)趕緊把測(cè)試補(bǔ)上吧!
聯(lián)系客服