當ng專案越來越大的時候,單元測試就要提上日程了,有的時候團隊是以測試先行,有的是先實現功能,後面再測試功能模組,這個各有利弊,今天主要說說利用karma和jasmine來進行ng模組的單元測試.
什麼是Karma
karma是一個單元測試的運行控制框架,提供以不同環境來運行單元測試,比如chrome,firfox,phantomjs等,測試框架支持jasmine,mocha,qunit,是一個以nodejs為環境的npm模組.
安裝測試相關的npm模組建議使用----save-dev參數,因為這是開發相關的,一般的運行karma的話只需要下面兩個npm指令
安裝karma的時候會自動的安裝一些常用的模組,參考karma程式碼裡的package.json檔案的peerDependencies屬性
然後一個典型的運行框架通常都需要一個設定檔,在karma裡可以是一個karma.conf.js,裡面的程式碼是一個nodejs風格的,一個普通的例子如下:
});
};
這裡要注意的時,上面的插件大部分都不需要單獨安裝,因為安裝karma的時候已經安裝了,這裡只有karma-junit-reporter導出插件需要單獨安裝,想要了解更多的關於配置文件的資訊可以,點這裡
karma就講到這裡,想了解更多關於它的資訊可以,點這裡
什麼是jasmine
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 syntgrite that youf clean, obvious syntgrite 🎜>
上面是jasmine官方文件裡對它的解釋,下面用中文簡單的翻譯下jasmine是一個行為驅動開發的測試框架,不依賴任何js框架以及dom,是一個非常乾淨以及友好API的測試庫.
下面簡單的以一個例子來說明它的用法
定義一個測試檔指令為test.js
foo = 0;
foo = 1;
});
foo = 0;
});
expect(foo).toEqual(1);
});
expect(foo).toEqual(1);
expect(true).toEqual(true);
});
});
2.it是用來定義單一特定測試任務,也有兩個參數,第一個用來描述測試內容,第二個參數是一個函數,裡面存放一些測試方法
3.expect主要用來計算一個變數或一個表達式的值,然後用來跟期望的值比較或做一些其它的事件
4.beforeEach與afterEach主要是用來在執行測試任務之前和之後做一些事情,上面的例子就是在執行之前改變變數的值,然後在執行完成之後重置變數的值
最後要說的是,describe函數裡的作用域跟普通JS一樣都是可以在裡面的子函數裡訪問的,就像上面的it訪問foo變量
想要執行上面的測試範例可以透過karar來運作,指令範例如下:
NG的單元測試
因為ng本身框架的原因,模組都是透過di來載入以及實例化的,所以為了方便配合jasmine來編寫測試腳本,所以官方提供了angular-mock.js的一個測試工具類別來提供模組定義,加載,注入等.下面說說ng-mock裡的一些常用方法
1.angular.mock.module 此方法同樣在window命名空間下,非常方便呼叫
module是用來配置inject方法注入的模組資訊,參數可以是字串,函數,物件,可以像下面這樣使用
$provide.value('version', 'TEST_VER');
}));
1.angular.mock.inject 此方法同樣在window命名空間下,非常方便呼叫
inject是用來注入上面配置好的ng模組,方面在it的測試函數裡調用,常見的調用例子如下:
描述('MyApp', function() {
// 您需要載入要測試的模組,
// 預設僅載入“ng”模組。
beforeEach(module('myApplicationModule'));
// Inject() 用於注入所有給定函數的參數
it('應該提供一個版本',inject(function(mode, version) {
Expect(version).toEqual('v1.0.1');
Expect(mode).toEqual('app');
}));
// 注入和模組方法也可以在 it 或 beforeEach
內部使用
it('應該覆寫一個版本並測試注入的新版本', function() {
// module() 接受函數或字串(模組別名)
模組(函數($提供){
$provide.value('版本', '覆蓋'); // 在這裡覆蓋版本
});
注入(函數(版本){
Expect(version).toEqual('覆蓋');
});
});
});
上面是官方提供的一些注入範例,程式碼很好看懂,其實inject裡面就是利用angular.inject方法創建的一個內建的依賴注入實例,然後裡面的模組注入跟普通ng模組裡的依賴處理是一樣的的
簡單的介紹完成ng-mock之後,我們下面分別以控制器,指令,過濾器來寫一個簡單的單元測試。
ng裡控制器的單元測試
定義了一個簡單的控制器
myApp.controller('MyController', function($scope) {
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
{"name":"jalapeno", "spiciness":"熱熱熱!"},
{"name":"habanero", "spiciness":"LAVA HOT!!"}];
$scope.spice = "你好費南!";
});
然後我們寫了一個測試腳本
描述('myController', function() {
var $scope;
beforeEach(module('myApp'));
beforeEach(注入(function($rootScope, $controller) {
$scope = $rootScope.$new();
$controller('MyController', {$scope: $scope});
}));
it('應以 3 種香料創造「香料」模型', function() {
Expect($scope.spices.length).toBe(3);
});
it('應該設定spice的預設值', function() {
Expect($scope.spice).toBe('你好費南!');
});
});
});
上面利用了$rootScope來建立子作用域,然後把這個參數傳進控制器的建置方法$controller裡去,最後會執行上面的控制器裡的方法,然後我們檢查子作用域裡的資料庫數量字串以及變數是否滿足期望的值。
想要了解更多關於ng裡控制器的信息,可以點擊這裡
ng裡指令的單元檢定
定義了一個簡單的指令
app.directive('aGreatEye', function () {
返回{
限制:'E',
替換:true,
範本:'
然後我們寫了一個簡單的測試腳本
// 載入 myApp 模組,其中包含指令
beforeEach(module('myApp'));
// 儲存對 $rootScope 和 $compile
的引用
// 所以它們可用於此描述區塊中的所有測試
beforeEach(注入(函數(_$compile_, _$rootScope_){
// 符合
時,注入器會解開參數名稱周圍的底線(_)
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('以適當的內容取代元素', function() {
// 編譯中包含指令
的HTML
var element = $compile("
// 觸發所有手錶,因此將計算範圍表達式 1
$rootScope.$digest();
// 檢查編譯的元素是否包含模板化內容
Expect(element.html()).toContain("無蓋,被火焰纏繞,2次");
});
});
上面的例子來自官方提供的,最後上面的指令將在html裡使用
測試腳本裡首先註入$compile和$rootScope兩個服務,一個用於編譯html,一個用於創建作用域用,注意這裡的_,默認ng裡注入的服務隨後加上_時,最後會被ng處理掉的,這兩個服務保存在內部的兩個變數裡,方便下面的測試實例能呼叫到
$compile方法決定原指令html,然後在傳回的函式中確定$rootScope,這樣就完成了作用域與視圖的綁定,最後呼叫$rootScope.$digest來觸發所有監聽,保證視圖裡的模型內容得到更新
然後取得目前指令對應元素的html內容與期望值進行比較。
想要了解更多關於ng裡的指令的信息,可以點擊這裡
ng裡的過濾器單元測試
定義了一個簡單的過濾器
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');
}));
});
});
上面的程式碼先配置過濾器模組,然後定義一個version值,因為interpolate依賴這個服務,最後用inject注入interpolate過濾器,注意這裡的過濾器後面得加上Filter後綴,最後傳入文本內容到濾波器函數裡執行,與期望值進行對比.
總結
利用測試來開發NG有很多好處,可以保證模組的穩定性,還有一點就是能夠深入的了解ng的內部運行機制,所以建議用ng開發的同學趕緊把測試補上吧!