Angular的自动化测试

2023-11-03


当Angular项目的规模到达一定的程度,就需要进行测试工作了。本文着重介绍关于ng的测试部分,主要包括以下三个方面:

  1. 框架的选择(Karma+Jasmine)
  2. 测试的分类和选择(单元测试 + 端到端测试)
  3. 在ng中各个模块如何编写测试用例

下面各部分进行详细介绍。

测试的分类

在测试中,一般分为单元测试端到端测试,单元测试是保证开发者验证代码某部分有效性的技术,端到端(E2E)是当你想确保一堆组件能按事先预想的方式运行起来的时候使用。

其中单元测试又分为两类: TDD(测试驱动开发)BDD(行为驱动开发)。下面着重介绍两种开发模式。

  • TDD(测试驱动开发 Test-driven development)是使用测试案例等来驱动你的软件开发。

    如果我们想要更深入点了解TDD,我们可以将它分成五个不同的阶段:

    1. 首先,开发人员编写一些测试方法。

    2. 其次,开发人员使用这些测试,但是很明显的,测试都没有通过,原因是还没有编写这些功能的代码来实际执行。

    3. 接下来,开发人员实现测试中的代码。

    4. 如果开发人员写代码很优秀,那么在下一阶段会看到他的测试通过。

    5. 然后开发人员可以重构自己的代码,添加注释,使其变得整洁,开发人员知道,如果新添加的代码破坏了什么,那么测试会提醒他失败。

      其中的流程图如下:
      TDD

      TDD

      TDD的好处:

    6. 能驱使系统最终的实现代码,都可以被测试代码所覆盖到,也即“每一行代码都可测”。

    7. 测试代码作为实现代码的正确导向,最终演变为正确系统的行为,能让整个开发过程更加高效。

  • BDD是(行为驱动开发 Behavior-Driven Development)指的是不应该针对代码的实现细节写测试,而是要针对行为写测试。BDD测试的是行为,即软件应该怎样运行。

    • 和TDD比起来,BDD是需要我们先写行为规范(功能明细),在进行软件开发。功能明细和测试看起来非常相似,但是功能明细更加含蓄一些。BDD采用了更详细的方式使得它看起来就像是一句话。

    • 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命令:

      
      
1
2
      
      
npm install karma -- save- dev
npm install karma-junit-reporter -- save- dev

然后一个典型的运行框架通常都需要一个配置文件,在karma里可以是一个karma.conf.js,里面的代码是一个nodejs风格的,一个普通的例子如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      
      
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'
}
});
};

运行时输入:

      
      
1
      
      
karma start test/karma .conf .js

jasmine

jasmine是一个行为驱动开发的测试框架,不依赖任何js框架以及dom,是一个非常干净以及友好API的测试库.

以下以一个具体实例说明test.js

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
      
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方法注入的模块信息。具体使用如下:
      
      
1
2
3
4
5
      
      
beforeEach( module('myApp.filters')) ;
beforeEach( module( function($provide) {
$provide.value('version', 'TEST_VER') ;
})) ;

该方法一般在beforeEach中使用,在执行测试用例之前可以获得模块的配置。

  • angular.mock.inject: inject用来注入配置好的ng模块,来供测试用例里进行调用。具体使用如下:
      
      
1
2
3
4
      
      
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的代码部分。

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
      
      
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()之后,笔记的数量应该加一。具体的单元测试如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
      
      
describe( 'TodoController Test', function() {
beforeEach(module( 'todoApp')); // 将会在所有的it()之前运行
// 我们在这里不需要真正的factory。因此我们使用一个假的factory。
var mockService = {
notes: [ 'note1', 'note2'], //仅仅初始化两个项目
get: function() {
return this.notes;
},
put: function(content) {
this.notes.push(content);
}
};
// 现在是真正的东西,测试spec
it( 'should return notes array with two elements initially and then add one',
inject( function($rootScope, $controller) { //注入依赖项目
var scope = $rootScope.$new();
// 在创建控制器的时候,我们也要注入依赖项目
var ctrl = $controller( 'TodoController', {$scope: scope, notesFactory:mockService});
// 初始化的技术应该是2
expect(scope.notes.length).toBe( 2);
// 输入一个新项目
scope.note = 'test3';
// 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用作创建新的控制器。

  1. $controller服务需要两个参数。第一个参数是将要创建的控制器的名称。第二个参数是一个代表控制器依赖项目的对象,
  2. $rootScope.$new()方法将会返回一个新的作用域,它用来注入控制器。同时我们传入mockService作为假factory。

之后,初始化会根据notes数组的长度预测笔记的数量,同时在执行了createNote()函数之后,会改变数组的长度,因此可以写出两个测试用例。

Factory部分

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

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
      
      
describe('notesFactory tests', function() {
var factory ;
// 在所有it()函数之前运行
beforeEach( function() {
// 载入模块
module('todoApp') ;
// 注入你的factory服务
inject( function( notesFactory) {
factory = notesFactory ;
}) ;
var store = {
todo1: 'test1',
todo2: 'test2',
todo3: 'test3'
} ;
spyOn( localStorage, 'getItem').andCallFake( function( key) {
return store[key] ;
}) ;
spyOn( localStorage, 'setItem').andCallFake( function( key, value) {
return store[key] = value + '' ;
}) ;
spyOn( localStorage, 'clear').andCallFake( function() {
store = {} ;
}) ;
spyOn( Object, 'keys').andCallFake( function( value) {
var keys=[] ;
for( var key in store) {
keys.push( key) ;
}
return keys ;
}) ;
}) ;
// 检查是否有我们想要的函数
it('should have a get function', function() {
expect( angular.isFunction( factory.get)).toBe( true) ;
expect( angular.isFunction( factory.put)).toBe( true) ;
}) ;
// 检查是否返回 3条记录
it('should return three todo notes initially', function() {
var result = factory.get() ;
expect( result.length).toBe( 3) ;
}) ;
// 检查是否添加了一条新纪录
it('should return four todo notes after adding one more', function() {
factory.put('Angular is awesome') ;
var result = factory.get() ;
expect( result.length).toBe( 4) ;
}) ;
}) ;

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

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

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

Filter部分

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

      
      
1
2
3
4
5
      
      
todoApp.filter( 'truncate', function(){
return function(input,length){
return ( input.length > length ? input.substring( 0,length) : input);
}
});

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

      
      
1
2
3
4
5
6
7
      
      
describe('filter test',function(){
beforeEach( module('todoApp')) ;
it('should truncate the input to 1o characters',inject( function( truncateFilter){
expect( truncateFilter('abcdefghijkl', 10).length).toBe( 10) ;
}) ;
) ;
}) ;

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

Directive部分

源码中的指令部分:

      
      
1
2
3
4
5
6
7
8
      
      
todoApp.directive( 'customColor', function() {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
elem.css({ 'background-color': attrs.customColor});
}
};
});

由于指令必须编译之后才能生成相关的模板,因此我们要引入$compile服务来完成实际的编译,然后再测试我们想要进行测试的元素。
angular.element()会创建一个jqLite元素,然后我们将其编译到一个新生成的自作用域中,就可以被测试了。具体测试用例如下:

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
      
      
describe( 'directive tests', function(){
beforeEach(module( 'todoApp'));
it( 'should set background to rgb(128, 128, 128)',
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( 'rgb(128, 128, 128)');
})
);
});

开始端到端测试

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

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

      
      
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
      
      
describe('my app', function() {
beforeEach( function() {
browser().navigateTo('../../app/notes.html') ;
}) ;
var oldCount = -1 ;
it( "entering note and performing click", function() {
element('ul').query( function($el, done) {
oldCount = $el.children().length ;
done() ;
}) ;
input('note').enter('test data') ;
element('button').query( function($el, done) {
$el.click() ;
done() ;
}) ;
}) ;
it('should add one more element now', function() {
expect( repeater('ul li').count()).toBe( oldCount + 1) ;
}) ;
}) ;

我们在端到端测试过程中,首先导航到我们的主html页面app/notes.html,可以通过browser.navigateTo()来完成,element.query()函数选择了ul元素并记录其中有多少个初始化的项目,存放在oldCount变量中。
然后通过input('note').enter()来键入一个新的笔记,然后模拟一下点击操作来检查是否增加了一个新的笔记(li元素)。然后通过断言可以将新旧的笔记数进行对比。

相关资料

tdd vs bdd

从tdd到bdd

jasmine框架介绍

jasmine框架介绍二

关于前端开发谈谈单元测试

前端测试探索实践

说说NG里的单元测试

在AngularJS中进行单元测试和端到端测试

来自:http://xgfe.github.io/2015/12/17/HeOH/AngularJS%E7%9A%84%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95/ 


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Angular的自动化测试 的相关文章

随机推荐

  • 使用JPA根据实体类生成数据库表

    springboot数据库 一 springboot JPA JPA springboot jpa 数据库的一系列的定义数据持久化的标准的体系 学习的目的是 利用springboot实现对数据库的操作 第一步 添加springboot da
  • 获取JavaScript时间戳函数的方法和js时间戳转时间方法

    文章目录 一 JavasCRIPT时间转时间戳 方法一 Date now 方法三 valueOf 方法四 getTime 方法五 Number 二 js时间戳转时间 方法一 生成 yyyy MM dd 上 下 午hh mm ss 格式 方法
  • java字符串

    java字符串 java字符串 一 String类 一 特点 二 构造方法 String str abc 与 String str2 new String abc 的区别 三 常用方法 intern String类拼接 字符串转数字 字符串
  • linux qt目录查看,QT遍历目录获取文件信息

    QFileInfo获取文件信息 文件名称 路径 大小 创建时间 修改时间 权限等使用路径 UNIX home dipper file1Windows C dipper file1 构造函数 QFileInfo fileInfo path Q
  • 【翻译 + 整理】Qt样式表详解(10):伪状态

    1 active 部件处于活动的状态 2 adjoins item 当QTreeView的 branch与某个item相邻时 将设置此状态 QTreeView branch background red QTreeView branch a
  • 蓝桥杯流转呼吸灯

    include STC15F2K60S2 h include
  • Python直接使用plot()函数画图

    目录 一 plot 函数的认识 二 plot 函数基本运用 三 plot 函数数据可视化画图以及图元基本参数设置 一 plot 函数的认识 在使用Python进行数据可视化编程中matplotlib库是我们用来对数据进行画图常用的第三方库
  • 时间戳转换成字符串,返回Invalid Date(自己遇到的坑)

    今天在开发的过程中 遇到一个比较坑自己的问题 将时间戳转换成正常日期的时候 总是会返回Invalid Date 排查了好久 在想为什么是这个结果 在控制台里面测试都是ok的呀 于是乎 想到了自己再后端定义的时候 时间戳定义的是字符串格式的数
  • Python 使用 Thrift 连接 HBASE 进行操作

    在工作中想要使用Python对HBASE进行操作 主要用来获取数据进行分析 HBASE提供了 Thrift 借口 通过查看API 进行了一些的尝试 下面就是使用Python的相关代码 在使用之前需要启动 HBASE的Thrift和安装pyt
  • 分布式系统设计的求生之路

    作者 作者 Simon 腾讯后台开发高级工程师 链接 http wetest qq com lab view id 105 著作权归作者所有 商业转载请联系WeTest获得授权 非商业转载请注明出处 分布式系统理念渐渐成为了后台架构技术的重
  • 嵌入式入门教学——C51(中)

    嵌入式入门教学汇总 嵌入式入门教学 C51 上 嵌入式入门教学 C51 中 嵌入式入门教学 C51 下 文章中所使用到的所有代码模块 免费 基于STC89C52RC的代码模块资源 CSDN文库 目录 七 矩阵键盘 八 定时器和中断 九 串口
  • win10常用操作集合 - vhd/wsl/等等

    文章目录 wsl常用操作 cli操作 vhd常用操作 UI操作 扩容 缩容 方法一 常规方法 方法二 碎片整理 常见问题1 win10 UI 基本配置 win10网络配置 防火墙配置 wsl常用操作 cli操作 前提 BIOS要使能虚拟化相
  • MATLAB搜索路径的查看和设置方法

    MATLAB搜索路径的查看和设置方法 1 查看matlab的搜索路径 单击matlab主界面菜单工具栏中的 设置路径 按钮 打开 设置路径 对话框 左侧的几个按钮用来添加目录到搜索路径 还可以从当前的搜索路径中移除选择的目录 右侧的列表框列
  • 静态代码检查-Sonar-环境安装(一)

    1 前提 1 安装mysql数据库 5 6以上版本 本人数据库版本5 7 2 安装jdk1 8 本人jdk版本1 8 2 官网下载 https www sonarqube org downloads 最新版本6 7稳定版 选择 Show a
  • 密码学 / 哈希算法

    一 诞生原因 在日常生活中 每个人去银行 坐火车都需要身份证证明自己的身份 身份证存在的目的就是要证明我真的是我 同样在网络中 一个文件是否被改过 更改之后就是新的文件 需要一个 身份证 证明 这里就需要了 hash 算法了 二 特点 为了
  • 黑马并发笔记

    参考这个就好 https www yuque com gaohanghang sgrbwh wng754 这个也不错 https blog csdn net weixin 50280576 article details 113033975
  • 开放加速规范AI服务器设计指南

    近日 在2023年开放计算社区中国峰会 OCP China Day 2023 上 开放加速规范AI服务器设计指南 以下简称 指南 发布 指南 面向生成式AI应用场景 进一步发展和完善了开放加速规范AI服务器的设计理论和设计方法 将助力社区成
  • Linux内存管理:ARM Memory Layout以及mmu配置

    http blog csdn net hongzg1982 article details 47341881 在内核进行page初始化以及mmu配置之前 首先需要知道整个memory map 1 ARM Memory Layout PAGE
  • Adobe Photoshop 2022版 功能介绍及使用技巧

    目录 版本介绍 使用技巧 截图展示 分享 版本介绍 Adobe Photoshop 2022是Adobe公司的一款专业的图像处理软件 它提供了强大的图像处理功能 从色彩调整 图层处理到高级合成等功能 新版本带来的一些更新包括 1 人工智能辅
  • Angular的自动化测试

    当Angular项目的规模到达一定的程度 就需要进行测试工作了 本文着重介绍关于ng的测试部分 主要包括以下三个方面 框架的选择 Karma Jasmine 测试的分类和选择 单元测试 端到端测试 在ng中各个模块如何编写测试用例 下面各部