基于karma+jasmine的web前端自动化测试
本帖最后由 悠悠小仙仙 于 2018-1-16 15:33 编辑名词解释
Node.js是一个基于ChromeV8引擎的JavaScript运行环境。Node.js使用了一个事件驱动、非阻塞式I/O的模型,使其轻量又高效。Node.js的包管理器npm,是全球最大的开源库生态系统。
Karma是一个基于Node.js的JavaScript测试执行过程管理工具(TestRunner)。该工具可用于测试所有主流Web浏览器,也可集成到CI(Continuousintegration)工具,也可和其他代码编辑器一起使用。这个测试工具的一个强大特性就是,它可以监控(Watch)文件的变化,然后自行执行,通过console.log显示测试结果。
Jasmine是一个简易的JS单元测试框架。Jasmine不依赖于任何浏览器、DOM、或者是任何JavaScript而存在。它适用于所有网站、Node.js项目,或者是任何能够在JavaScript上面运行的程序。
环境安装
首先必须安装执行环境nodejs
安装浏览器,推荐chrome(用于运行监听程序,监听js文件变化,自动触发测试执行)
安装karma+jasmine
npminstallkarma-g
npminstallkarma-jasmine-g
npminstallkarma-chrome-launcher-g
npminstallkarma-cli-g
npminstallkarma-coverage-g
npminstallkarma-html-reporter-g
安装完成后执行karma-v,检查安装是否正常
工程配置
可以使用karmainit,自动生成配置文件,完成部分参数的设置,然后再手动修改。
当然最快的配置方法,复制下面的配置//Karmaconfiguration
//GeneratedonTueNov01201614:17:00GMT+0800(中国标准时间)
module.exports=function(config){
config.set({
//basepaththatwillbeusedtoresolveallpatterns(eg.files,exclude)
basePath:'',
//frameworkstouse
//availableframeworks:https://npmjs.org/browse/keyword/karma-adapter
frameworks:['jasmine'],
//listoffiles/patternstoloadinthebrowser
//需要加载入浏览器的js文件,包括基础的类库,被测试js文件和测试用例js文件
//如果需要测试angular代码,比如引入angular-mock.js,给angular代码进行mock。
//注意angular-mock的版本一定要和angular版本一致。可在cdn网站对应的angular版本列表中寻找
files:[
'../webapp/vender/jquery/jquery-1.10.2.min.js',
'../webapp/vender/angular/angular.min.js',
'../webapp/vender/angular/angular-ui-router.min.js',
'lib/angular-mocks.js',
'../webapp/common/*.js',
'../webapp/commont/template/*.html',
'tc/ut/**/*.js'
],
//listoffilestoexclude
exclude:[
//'../webapp/vender/**/*.js'
],
//testresultsreportertouse
//possiblevalues:'dots','progress'
//availablereporters:https://npmjs.org/browse/keyword/karma-reporter
//这里定义输出的报告
//html对应karma-html-reporter组件,输出测试用例执行报告
//coverage对应karma-coverage组件,输出测试用例执行报告
reporters:['progress','html','junit','coverage'],
junitReporter:{
//willberesolvedtobasePath(inthesamewayasfiles/excludepatterns)
outputFile:'report/ut/test-results.xml',
suite:'UT',
useBrowserName:false
},
htmlReporter:{
outputDir:'report/ut',
reportName:'result'//outputDir+reportName组成完整的输出报告格式,如没有定义,会自动生成浏览器+OS信息的文件夹,不方便读取报告
},
//定义需要统计覆盖率的文件
preprocessors:{
'../webapp/common/*.js':'coverage',
'../webapp/common/template/*.html':'ng-html2js'
},
coverageReporter:{
type:'html',//将覆盖率报告类型type设置为cobertura或者html
subdir:'coverage',//dir+subdir组成完整的输出报告格式,如没有定义,会自动生成浏览器+OS信息的文件夹,不方便读取报告
dir:'report/ut/'//代码覆盖率报告生成地址
},
//webserverport
port:9876,
//enable/disablecolorsintheoutput(reportersandlogs)
colors:true,
//leveloflogging
//possiblevalues:config.LOG_DISABLE||config.LOG_ERROR||config.LOG_WARN||config.LOG_INFO||config.LOG_DEBUG
logLevel:config.LOG_INFO,
//enable/disablewatchingfileandexecutingtestswheneveranyfilechanges
//karma自动自动监视被测试文件和测试用用例文件,如有修改,自动重新执行测试
autoWatch:true,
//ContinuousIntegrationmode
//iftrue,Karmacapturesbrowsers,runsthetestsandexits
//上一个参数为true,本参数为false,,则自动监视才生效。否则执行完测试用例后自动退出
singleRun:true,
//startthesebrowsers
//availablebrowserlaunchers:https://npmjs.org/browse/keyword/karma-launcher
//用来执行自动监听的浏览器,推荐chrome
browsers:['Chrome'],
//Concurrencylevel
//howmanybrowsershouldbestartedsimultaneous
concurrency:Infinity,
//自动将模板文件路径转换页面引入路径,以便注入用例中
ngHtml2JsPreprocessor:{
cacheIdFromPath:function(filepath){
varcacheId=filepath.substr(filepath.lastIndexOf('/webapp/')+7);
//console.log(cacheId);
returncacheId;
},
moduleName:'template'
}
})
}
保存配置文件到测试目录
测试用例编写
1、用例怎么写
describe("AsuiteofCommon/common.js",function(){
beforeAll(function(){
console.log('beforeAll');
});
describe("extendsofString",function(){
varexpected;
beforeEach(function(){
expected='abcd';
});
it("trim",function(){
expect(expected).toEqual(("abcd").trim());
});
it("ltrim",function(){
expect(expected).toEqual(("abcd").ltrim());
});
it("rtrim",function(){
expect(expected).toEqual(("abcd").rtrim());
});
});
});
上述例子中,
a.describe相当于一个测试套,可以嵌套。
b.it('tcname',function(){})是一个测试用例。
c.beforeAll和beforeEach是预置条件,前者一个测试套执行一次,后者每个测试用例执行一次。
d.当然还会有afterAll和afterEach
e.expect是断言
2、断言都有那些比较
Matcher实现了断言的比较操作,将Expectation传入的实际值和Matcher传入的期望值比较。任何Matcher都能通过在expect调用Matcher前加上not来实现一个否定的断言(expect(a).not().toBe(false);)。
常用的Matchers有:
toBe():相当于==比较。
toNotBe():相当于!=比较。
toBeDefined():检查变量或属性是否已声明且赋值。
toBeUndefined()
toBeNull():是否是null。
toBeTruthy():如果转换为布尔值,是否为true。
toBeFalsy()
toBeLessThan():数值比较,小于。
toBeGreaterThan():数值比较,大于。
toEqual():相当于==,注意与toBe()的区别。一个新建的Object不是(nottobe)另一个新建的Object,但是它们是相等(toequal)的。
expect({}).not().toBe({});
expect({}).toEqual({});
toNotEqual()
toContain():数组中是否包含元素(值)。只能用于数组,不能用于对象。
toBeCloseTo():数值比较时定义精度,先四舍五入后再比较。
toHaveBeenCalled()
toHaveBeenCalledWith()
toMatch():按正则表达式匹配。
toNotMatch()
toThrow():检验一个函数是否会抛出一个错误
3、angular代码怎么写
先看例子
describe('ApplyMainCtrl',function(){
var$scope,
$controller,
$httpBackend;
varMainCtrl;
beforeEach(module('applyApp'));
beforeEach(inject(function(_$controller_,$rootScope,_$httpBackend_){
$scope=$rootScope.$new();
$httpBackend=_$httpBackend_;
$controller=_$controller_;
$httpBackend.when('POST',/\/api\/wxcop\/common\/record.*/).respond({});
}));
it('Check$scopeassignments.',function(){
MainCtrl=$controller('MainController',{
$scope:$scope
});
$httpBackend.flush();
$scope.gotoApplyHome();
$scope.judgeLogin();
expect($scope.typeSelect).toEqual(["单行文本","多行文本","单选","多选"]);
expect($scope.getItemItems("1,2,3")).toEqual(["1","2","3"]);
});
});
注意,要测试angular必须引入angular-mock。
说明
beforeEach(module('applyApp'));引入module'applyApp'
beforeEach(inject(function($controller,$rootScope,$httpBackend)依赖注入和http测试打桩
$controller('MainController',)初始化controller
直接调用scope,然后执行断言
5、angular的相关特性如何测试
1、测试函数
a.被测试代码
$scope.functionTriger=false;
$scope.doTest=function(){
$scope.functionTriger=true;
}
b.测试用例
it('function',function(){
expect($scope.functionTriger).toBeFalsy();
$scope.doTest();
expect($scope.functionTriger).toBeTruthy();
});
2、测试监听
a.被测试代码
$scope.watchVar=false;
$scope.watchedTrigeIndex=0;
$scope.$watch('watchVar',function(){
$scope.watchedTrigeIndex++;
});
b.测试用例
it('watch',function(){
expect($scope.watchedTrigeIndex).toBe(0);
$scope.watchVar=true;
$scope.$digest();
expect($scope.watchedTrigeIndex).toBe(1);
$scope.watchVar=false;
$scope.$digest();
expect($scope.watchedTrigeIndex).toBe(2);
});
3、测试广播
a.被测试代码
$scope.isHaveTriger=false;
$scope.$on('ngRepeatFinished',function(){
$scope.isHaveTriger=true;
});
b.测试用例
it('broadcast',function(){
expect($scope.isHaveTriger).toBeFalsy();
$scope.$broadcast('ngRepeatFinished');
expect($scope.isHaveTriger).toBeTruthy();
});
4、测试路由切换
a.被测试代码
.config(['$stateProvider','$urlRouterProvider',function($stateProvider,$urlRouterProvider){
$urlRouterProvider.otherwise("/detail");
$stateProvider.state('detail',{
url:'/detail',
template:'<p></p>',
controller:'MainCtrl'
});
}])
b.测试用例
it('route',function(){
inject(function(_$injector_){
$state=_$injector_.get('$state');
});
varcurState=$state.get('detail');
expect(curState.name).toEqual('detail');
expect(curState.url).toEqual('/detail');
expect(curState.controller).toEqual('MainCtrl');
expect(curState.template).toEqual('<p></p>');
});
5、测试过滤器
a.被测试代码
.filter('myFilter',function(){
returnfunction(data){
returndata+'lzw';
}
})
b.测试用例
it('filter',function(){
inject(function(_$injector_){
$filter=_$injector_.get('$filter');
});
6、测试service
a.被测试代码
.service('foo',function(){
varthisIsPrivate="Private";
functiongetPrivate(){
returnthisIsPrivate;
}
return{
variable:"Thisispublic",
getPrivate:getPrivate
};
})
b.测试用例
it('service',function(){
varfoo;
inject(function(_foo_){
foo=_foo_;
});
expect(foo.variable).toBe('Thisispublic');
expect(foo.getPrivate()).toBe('Private');
});
7、测试指令
a.被测试代码
.directive('myDirective',function(){
return{
restrict:'A',
replace:true,
template:'<p>11</p>',
link:function(scope){}
};
})
.directive('dirButton',function(){
return{
template:'<button>Incrementvalue!</button>',
link:function(scope,elem){
elem.find('button').on('click',function(){
scope.value++;
});
}
};
})
.directive('dirScope',function(){
return{
scope:{
config:'=',
notify:'@',
onChange:'&'
},
link:function(scope){
}
};
})
b.测试用例
it('directive',function(){
varlink=$compile('<pmy-directive></p>');
varelement=link($scope);
expect($(element).html()).toBe('11');
});
it('buttondirective',function(){
vardirectiveElem=$compile('<buttondir-button></button>')($scope);
$scope.value=10;
varbutton=directiveElem.find('button');
button.triggerHandler('click');
$scope.$digest();
expect($scope.value).toEqual(11);
});
it('scopedirective',function(){
$scope.config={
prop:'value'
};
$scope.notify=true;
$scope.onChange=jasmine.createSpy('onChange');
vardirectiveElem=$compile(angular.element('<pdir-scopeconfig="config"notify="notify"on-change="onChange()"></p>'))($scope);
$scope.$digest();
varisolatedScope=directiveElem.isolateScope();
//test=
isolatedScope.config.prop="value2";
expect($scope.config.prop).toEqual('value2');
//test@
isolatedScope.notify=false;
expect($scope.notify).toEqual(true);
//test&
expect(typeof(isolatedScope.onChange)).toEqual('function');
isolatedScope.onChange();
expect($scope.onChange).toHaveBeenCalled();
//调用指令的父controller。
directiveElem.scope().doFunction();
});
关于mock
$httpBackend
$httpBackend对于代码中的http请求进行mock。常用方法:
$httpBackend.when(method,url,,);
$httpBackend.expect(method,url,,);
when和expect都有对应的快捷方法:
whenGET(url,);
whenHEAD(url,);
whenDELETE(url,);
whenPOST(url,,);
whenPUT(url,,);
whenJSONP(url);
expectGET(url,);
expectHEAD(url,);
expectDELETE(url,);
expectPOST(url,,);
expectPUT(url,,);
expectPATCH(url,,);
expectJSONP(url);
url支持正则,比如:
$httpBackend.when('POST',/\/api\/wxcop\/common\/record.*/).respond({});
注意:
$httpBackend.when与$httpBackend.expect的区别在于:$httpBackend.expect的伪后台只能被调用一次(调用一次后会被清除),第二次调用就会报错,而且$httpBackend.resetExpectations可以移除所有的expect而对when没有影响。
常见异常处理
Argument'MainCtrl'isnotafunction,gotundefined
无法找到MainCtrl。可能原因:controller定义错误,app注入失败。
Disconnected,becausenomessagein10000ms.
ajax请求超时。原因:$httpBackend.flush();要放在ajax发起请求后执行。
指令采用templateUrl方式加载模板失败
可采用karma-ng-html2js-preprocessor插件自动注入。也可以采用$templateCache注入。注意,这两种方式都不支持模糊匹配 :victory:
页:
[1]