使用Jasmine和Karma对AngularJS页面程序进行测试


Posted in Javascript onMarch 05, 2016

AngularJS是继jQuery之后发生在JavaScript上最好的东西。这也是JavaScript开发一直以来想要的方式。Angular主要的优点之一就是它的依赖注入(Dependency Injection),它非常利于代码的单元测试。但有点小怪异的是,我在无论如何都没能找到一个介绍如何做单元测试的教程。

当然有很多不错的推荐:使用Jasmine测试框架和Karma测试执行器(Test Runner);但是并没有一篇完整的从无到有指导如何测试的教程。所以我写了这篇文章。我在网上找了很多资源才知道如何去做,而你现在不需要去做这些(如果一开始就看到这篇文章的话)。

请告诉我你看到的任何错误,直到我能说这是基于Karma和Jasmine测试Angular应用的最佳实践。

介绍

这篇文章会引导你安装使用Karma和Jasmine做自动化测试所需要的所有工具。我不在乎你实在使用TDD(测试驱动开发)还是TAD(测试辅助开发),在这篇文章中,我假设你已经有一个文件需要测试。

安装Karma

如果你没有安装 Node.js,那么请自行下载和安装。安装之后,打开终端或命令行输入一下命令:

npm install -g karma

文件结构

文件结构是跟我们的议题关联不大,但是在接下来的测试中,我使用的文件结构如下:

Application
| angular.js
| angular-resource.js
| Home
 | home.js
| Tests
 | Home
 | home.tests.js
 | karma.config.js (will be created in the next step)
 | angular-mocks.js

*我并不主张这种文档结构,我展示它只是为了测试举例。

配置Karma

切换到你想要放置配置文件的目录,然后在终端中输入下面的命令来创建配置文件:

karma init karma.config.js

你会被询问一些问题,包括你想使用那个测试框架,你是否需要自动监测文件,包含哪些测试和被测试文件等。在我们的教程中,我们保留‘Jasmine'作为我们默认的框架,开启文件自动监测,并包含下面的文件:

../*.js
../**.*.js
angular-mocks.js
**/*.tests.js

这些都是相对路径,包含了1)父目录下的所有.js文件,2)父目录下的所有子目录下的所有.js文件,3)当前目录下的angular-mock.js,4)以及当前目录(包含子目录)下所有的.tests.js文件(我喜欢以这样的方式来区分测试文件和其他的文件)。

不管你选择了什么文件,请确保你引入了 angular.js,angular-mock.js,以及其他你需要使用的文件。

启动Karma

现在已经可以启动Karma了,依然在终端中输入:

karma start karma.config.js

这个命令会在你的电脑上启动你在配置文件中列出的浏览器。这些浏览器都会以socket的方式连接到Karma的实例上,你会看到一组活动的浏览器并被告知她们是否在执行测试。我但愿Karma已经告诉你在每个浏览器上的最终测试结果总结(比如16个中的15个通过,1个失败),遗憾的是你只能通过终端窗口看到这些信息。

Karma的一个很突出的特色是你可以在网络中使用任何设备连接并测试你的代码。试一下将你的手机浏览器指向Karma服务,你可以在电脑上任何一个运行的浏览器上找到这个测试的URL地址。它应该类似于:http://localhost:9876/?id=5359192。你可以将你的手机,虚拟机,或其他任何设备的浏览器指向 [你在网络上的IP地址]:9876/?id=5359192. 因为Karma是在运行一个 Node.js 实例,你的测试机器就像一个web服务器一样,会将测试发送到任何指向它的浏览器。

基本的测试

我们假设你已经有一个文件需要测试。我们要使用到的 home.js 文件如下:

home.js

'use strict';
 
var app = angular.module('Application', ['ngResource']);
 
app.factory('UserFactory', function($resource){
 return $resource('Users/users.json')
});
 
app.controller('MainCtrl', function($scope, UserFactory) {
 $scope.text = 'Hello World!';
 $scope.users = UserFactory.get();
});

我们可以在 home.test.js 文件中创建我们的测试用例。我们从简单的那个测试开始:$scope.text 应该等于 ‘Hello World!'。 为了完成这个测试,我们需要模拟我们的 Application 模块以及 $scope 变量。我们会在Jasmine的 beforeEach 方法中做这个工作,这样的话我们在每个测试用例开始时可以有一个全新的(干净的)controler和scope对象。

home.tests.js

'use strict';
 
describe('MainCtrl', function(){
 var scope;
//我们会在测试中使用这个scope
 
 
//模拟我们的Application模块并注入我们自己的依赖
 beforeEach(angular.mock.module('Application'));
 
//模拟Controller,并且包含 $rootScope 和 $controller
 beforeEach(angular.mock.inject(function($rootScope, $controller){
  
//创建一个空的 scope
  scope = $rootScope.$new();
  
//声明 Controller并且注入已创建的空的 scope
  $controller('MainCtrl', {$scope: scope});
 });
 
// 测试从这里开始
});

从代码中你可以看到我们注入了我们自己的 scope,因此我们可以在它的外部验证它的信息。同时,别忘了模拟模块本身(第7行代码)!我们现在已经为测试做好了准备:

home.tests.js

// 测试从这里开始
it('should have variable text = "Hello World!"', function(){
 expect(scope.text).toBe('Hello World!);
});

如果你运行这个测试,它可以在任何指向Karma的浏览器中执行,并且测试通过。

发送$resource请求

现在我们已经准备好测试 $resource 请求。要完成这个请求,我们需要使用到 $httpBackend, 它一个模拟版本的Angular $http。我们会创建另一个叫做 $httpBackend 的变量,在第二个 beforEach块中,注入 _$httpBackend_ 并将新创建的变量指向 _$httpBackend_。接下来我们会告诉 $httpBackend 如何对请求做出响应。

$httpBackend = _$httpBackend_; 
$httpBackend.when('GET', 'Users/users.json').respond([{id: 1, name: 'Bob'}, {id:2, name: 'Jane'}]);

我们的测试: home.tests.js

it('should fetch list of users', function(){
   $httpBackend.flush();
   expect(scope.users.length).toBe(2);
   expect(scope.users[0].name).toBe('Bob');
  });

都放到一起

home.tests.js

'use strict';
 
describe('MainCtrl', function(){
 var scope, $httpBackend;
//we'll use these in our tests
 
 
//mock Application to allow us to inject our own dependencies
 beforeEach(angular.mock.module('Application'));
 
//mock the controller for the same reason and include $rootScope and $controller
 beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_){
  $httpBackend = _$httpBackend_;
  $httpBackend.when('GET', 'Users/users.json').respond([{id: 1, name: 'Bob'}, {id:2, name: 'Jane'}]);
 
  
//create an empty scope
  scope = $rootScope.$new();
  
//declare the controller and inject our empty scope
  $controller('MainCtrl', {$scope: scope});
 });
 
// tests start here
 it('should have variable text = "Hello World!"', function(){
  expect(scope.text).toBe('Hello World!');
 });
 it('should fetch list of users', function(){
  $httpBackend.flush();
  expect(scope.users.length).toBe(2);
  expect(scope.users[0].name).toBe('Bob');
 });
});

技巧

Karma会运行所有文件中的所有测试用例,如果你只想运行所有测试的一个子集,修改 describe 或 it 为 ddescribe 或 iit 来运行个别的一些测试。如果有些测试你不想运行他们,那么修改 describe 或 it 为 xdescribe 或 xit 来忽略这些代码。

你也可以在html文件的页面上运行你的测试。举例的代码如下:
home.runner.html

<!DOCTYPE html>
<html>
<head>
 <title>Partner Settings Test Suite</title>
 
<!-- include your script files (notice that the jasmine source files have been added to the project) -->
 <script type="text/javascript" src="../jasmine/jasmine-1.3.1/jasmine.js"></script>
 <script type="text/javascript" src="../jasmine/jasmine-1.3.1/jasmine-html.js"></script>
 <script type="text/javascript" src="../angular-mocks.js"></script>
 <script type="text/javascript" src="home.tests.js"></script>
 <link rel="stylesheet" href="../jasmine/jasmine-1.3.1/jasmine.css"/>
</head>
<body>
 
<!-- use Jasmine to run and display test results -->
 <script type="text/javascript">
  var jasmineEnv = jasmine.getEnv();
  jasmineEnv.addReporter(new jasmine.HtmlReporter());
  jasmineEnv.execute();
 </script>
</body>
</html>
Javascript 相关文章推荐
jQuery 位置插件
Dec 25 Javascript
基于JQuery的类似新浪微博展示信息效果的代码
Jul 23 Javascript
图片放大镜jquery.jqzoom.js使用实例附放大镜图标
Jun 19 Javascript
JS实现网页表格自动变大缩小的方法
Mar 09 Javascript
jQuery select自动选中功能实现方法分析
Nov 28 Javascript
使用JavaScript实现一个小程序之99乘法表
Sep 21 Javascript
js中url对象化管理分析
Dec 29 Javascript
Vue组件和Route的生命周期实例详解
Feb 10 Javascript
小程序实现自定义导航栏适配完美版
Apr 02 Javascript
原生js实现无缝轮播图
Jan 11 Javascript
vue-cli点击实现全屏功能
Mar 07 Javascript
JavaScript封装单向链表的示例代码
Sep 17 Javascript
JavaScript的React框架中的JSX语法学习入门教程
Mar 05 #Javascript
在AngularJS框架中处理数据建模的方式解析
Mar 05 #Javascript
简单讲解AngularJS的Routing路由的定义与使用
Mar 05 #Javascript
整理AngularJS框架使用过程当中的一些性能优化要点
Mar 05 #Javascript
详解JavaScript的AngularJS框架中的表达式与指令
Mar 05 #Javascript
深入解析AngularJS框架中$scope的作用与生命周期
Mar 05 #Javascript
JS判断字符串字节数并截取长度的方法
Mar 05 #Javascript
You might like
PHP5 的对象赋值机制介绍
2011/08/02 PHP
PHP实现的下载远程图片自定义函数分享
2015/01/28 PHP
解决thinkphp5未定义变量会抛出异常,页面错误,请稍后再试的问题
2019/10/16 PHP
Javascript 继承实现例子
2009/08/12 Javascript
js模仿windows桌面图标排列算法具体实现(附图)
2013/06/16 Javascript
使用jquery实现以post打开新窗口
2014/03/19 Javascript
node.js中的http.response.writeHead方法使用说明
2014/12/14 Javascript
基于jQuery日历插件制作日历
2016/03/11 Javascript
webuploader模态框ueditor显示问题解决方法
2016/12/27 Javascript
vue.js实现请求数据的方法示例
2017/02/07 Javascript
jQuery插件之validation插件
2017/03/29 jQuery
JavaScript基本语法_动力节点Java学院整理
2017/06/26 Javascript
JavaScript实现为事件句柄绑定监听函数的方法分析
2017/11/14 Javascript
webpack手动配置React开发环境的步骤
2018/07/02 Javascript
每个 JavaScript 工程师都应懂的33个概念
2018/10/22 Javascript
JavaScript 中判断变量是否为数字的示例代码
2020/10/22 Javascript
在antd Form表单中select设置初始值操作
2020/11/02 Javascript
[59:00]DOTA2-DPC中国联赛 正赛 Ehome vs PSG.LGD BO3 第一场 3月7日
2021/03/11 DOTA
用python的requests第三方模块抓取王者荣耀所有英雄的皮肤实例
2017/12/14 Python
Python实现求一个集合所有子集的示例
2018/05/04 Python
Python实现的多进程拷贝文件并显示百分比功能示例
2019/04/09 Python
基于tensorflow指定GPU运行及GPU资源分配的几种方式小结
2020/02/03 Python
基于Python生成个性二维码过程详解
2020/03/05 Python
css3中背景尺寸background-size详解
2014/09/02 HTML / CSS
理肤泉加拿大官网:La Roche-Posay加拿大
2018/07/06 全球购物
澳大利亚拥有最好的家具和家居用品在线目的地:Nestz
2019/02/23 全球购物
豪华床上用品 :Jennifer Adams
2019/09/15 全球购物
美国传奇滑手Paul Rodriguez创办的街头滑板品牌:Primitive Skateboarding
2019/10/29 全球购物
学校联谊活动方案
2014/02/15 职场文书
爱我中华演讲稿
2014/05/20 职场文书
质检员岗位职责范本
2015/04/07 职场文书
2015年公司工作总结
2015/04/25 职场文书
因身体原因离职的辞职信范文
2015/05/12 职场文书
大学生自我鉴定怎么写
2019/05/07 职场文书
成人成长感言如何写?
2019/08/16 职场文书
2021年国产动漫公司排行前十名,玄机科技上榜,第二推出过铠甲勇士
2022/03/18 杂记