当ng
项目越来越大的时候,单元测试就要提上日程了,有的时候团队是以测试先行,有的是先实现功能,后面再测试功能模块,这个各有利弊,今天主要说说利用karma
和jasmine
来进行ng
模块的单元测试.
karma
是一个单元测试的运行控制框架,提供以不同环境来运行单元测试,比如chrome
,firfox
,phantomjs
等,测试框架支持jasmine
,mocha
,qunit
,是一个以nodejs
为环境的npm
模块.
安装测试相关的npm
模块建议使用----save-dev
参数,因为这是开发相关的,一般的运行karma
的话只需要下面两个npm
命令
安装karma
的时候会自动的安装一些常用的模块,参考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"
}
然后一个典型的运行框架通常都需要一个配置文件,在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
的时候已经安装了,这里只有karma-junit-reporter
导出插件需要单独安装,想要了解更多的关于配置文件的信息可以,点击这里
karma
就讲到这里,想了解更多关于它的信息可以,点击这里
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
官方文档里对它的解释,下面用中文简单的翻译下
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);
});
});
上面的例子来自于官网,这里只说下几个重要的API,更多的用法请,点击这里
首先任何一个测试用例以describe
函数来定义,它有两参数,第一个用来描述测试大体的中心内容,第二个参数是一个函数,里面写一些真实的测试代码
it
是用来定义单个具体测试任务,也有两个参数,第一个用来描述测试内容,第二个参数是一个函数,里面存放一些测试方法
expect
主要用来计算一个变量或者一个表达式的值,然后用来跟期望的值比较或者做一些其它的事件
beforeEach
与afterEach
主要是用来在执行测试任务之前和之后做一些事情,上面的例子就是在执行之前改变变量的值,然后在执行完成之后重置变量的值
最后要说的是,describe
函数里的作用域跟普通JS一样都是可以在里面的子函数里访问的,就像上面的it
访问foo
变量
想要运行上面的测试例子可以通过karar
来运行,命令例子如下
karma start test/karma.conf.js
下面我们重点的说说ng
里的控制器,指令,服务模块的单元测试.
因为ng
本身框架的原因,模块都是通过di
来加载以及实例化的,所以为了方便配合jasmine
来编写测试脚本,所以官方提供了angular-mock.js
的一个测试工具类来提供模块定义,加载,注入等.
下面说说ng-mock
里的一些常用方法
module
是用来配置inject
方法注入的模块信息,参数可以是字符串,函数,对象,可以像下面这样使用
beforeEach(module('myApp.filters'));
beforeEach(module(function($provide) {
$provide.value('version', 'TEST_VER');
}));
它一般用在beforeEach
方法里,因为这个可以确保在执行测试任务的时候,inject
方法可以获取到模块配置
inject
是用来注入上面配置好的ng
模块,方面在it
的测试函数里调用,常见的调用例子如下
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
例子,代码很好看懂,其实inject
里面就是利用angular.inject
方法创建的一个内置的依赖注入实例,然后里面的模块注入跟普通ng
模块里的依赖处理是一样的
简单的介绍完ng-mock
之后,下面我们分别以控制器,指令,过滤器来编写一个简单的单元测试.
ng里控制器的单元测试
定义一个简单的控制器
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!";
});
然后我们编写一个测试脚本
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
来创建子作用域,然后把这个参数传进控制器的构建方法$controller
里去,最终会执行上面的控制器里的方法,然后我们检查子作用域里的数组数量以及字符串变量是否跟期望的值相等.
想要了解更多关于ng
里的控制器的信息,可以点击这里
ng里指令的单元测试
定义一个简单的指令
var app = angular.module('myApp', []);
app.directive('aGreatEye', function () {
return {
restrict: 'E',
replace: true,
template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
};
});
然后我们编写一个简单的测试脚本
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");
});
});
上面的例子来自于官方提供的,最终上面的指令将会这用在html
里使用
<a-great-eye></a-great-eye>
测试脚本里首先注入$compile
与$rootScope
两个服务,一个用来编译html
,一个用来创建作用域用,注意这里的_
,默认ng
里注入的服务前后加上_
时,最后会被ng
处理掉的,这两个服务保存在内部的两个变量里,方便下面的测试用例能调用到
$compile
方法传入原指令html
,然后在返回的函数里传入$rootScope
,这样就完成了作用域与视图的绑定,最后调用$rootScope.$digest
来触发所有监听,保证视图里的模型内容得到更新
然后获取当前指令对应元素的html
内容与期望值进行对比.
想要了解更多关于ng
里的指令的信息,可以点击这里
ng里的过滤器单元测试
定义一个简单的过滤器
var app = angular.module('myApp', []);
app.filter('interpolate', ['version', function(version) {
return function(text) {
return String(text).replace(/\%VERSION\%/mg, version);
};
}]);
然后编写一个简单的测试脚本
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');
}));
});
});
上面的代码先配置过滤器模块,然后定义一个version
值,因为interpolate
依赖这个服务,最后用inject
注入interpolate
过滤器,注意这里的过滤器后面得加上Filter
后缀,最后传入文本内容到过滤器函数里执行,与期望值进行对比.
利用测试来开发NG有很多好处,可以保证模块的稳定性,还有一点就是能够深入的了解ng的内部运行机制,所以建议用ng开发的同学赶紧把测试补上吧!
扫码关注w3ctech微信公众号
建议md里的跳转可以改成新页面打开比较好,嘿嘿
你是说链接吗?
对呀,就是链接
共收到3条回复