使用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 相关文章推荐
input 高级限制级用法
Mar 26 Javascript
JavaScript在IE和Firefox浏览器下的7个差异兼容写法小结
Jun 18 Javascript
js写出遮罩层登陆框和对联广告并自动跟随滚动条滚动
Apr 29 Javascript
JavaScript中双叹号!!作用示例介绍
Sep 21 Javascript
微信+angularJS的SPA应用中用router进行页面跳转,jssdk校验失败问题解决
Sep 09 Javascript
用js实现before和after伪类的样式修改的示例代码
Sep 07 Javascript
layui实现动态和静态分页
Apr 28 Javascript
又拍云 Node.js 实现文件上传、删除功能
Oct 28 Javascript
详解基于 Node.js 的轻量级云函数功能实现
Jul 08 Javascript
jQuery实现验证用户登录
Dec 10 jQuery
JS实现前端路由功能示例【原生路由】
May 29 Javascript
JS如何操作DOM基于表格动态展示数据
Oct 15 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
解析PHP 使用curl提交json格式数据
2013/06/29 PHP
php遍历目录输出目录及其下的所有文件示例
2014/01/27 PHP
使用配置类定义Codeigniter全局变量
2014/06/12 PHP
PHP调用C#开发的dll类库方法
2014/07/28 PHP
分享PHP守护进程类
2015/12/30 PHP
PHP连接MSSQL方法汇总
2016/02/05 PHP
详谈PHP中public,private,protected,abstract等关键字的用法
2017/12/31 PHP
javascript脚本编程解决考试分数统计问题
2008/10/18 Javascript
jQuery-Easyui 1.2 实现多层菜单效果的代码
2012/01/13 Javascript
jquery实现最简单的滑动菜单效果代码
2015/09/12 Javascript
JavaScript实现清空(重置)文件类型INPUT元素值的方法
2016/11/17 Javascript
Vue 2.0中生命周期与钩子函数的一些理解
2017/05/09 Javascript
详解vue中computed 和 watch的异同
2017/06/30 Javascript
JavaScript基础教程之如何实现一个简单的promise
2018/09/11 Javascript
Vuejs监听vuex中值的变化的方法示例
2018/12/02 Javascript
微信小程序实现的五星评价功能示例
2019/04/25 Javascript
原生JS使用Canvas实现拖拽式绘图功能
2019/06/05 Javascript
微信小程序实现手势滑动卡片效果
2019/08/26 Javascript
vue轮播组件实现$children和$parent 附带好用的gif录制工具
2019/09/26 Javascript
Vue路由的模块自动化与统一加载实现
2020/06/05 Javascript
python各种语言间时间的转化实现代码
2016/03/23 Python
Python的requests网络编程包使用教程
2016/07/11 Python
Django项目中model的数据处理以及页面交互方法
2018/05/30 Python
详解如何管理多个Python版本和虚拟环境
2019/05/10 Python
python3中rank函数的用法
2019/11/27 Python
客服端调用EJB对象的几个基本步骤
2012/01/15 面试题
毕业生个人求职信范例分享
2013/12/17 职场文书
互联网创业计划书的书写步骤
2014/01/28 职场文书
党员批评与自我批评(5篇)
2014/09/23 职场文书
2014年重阳节老干部座谈会上的讲话稿
2014/09/25 职场文书
社区领导班子四风问题原因分析及整改措施
2014/09/28 职场文书
2014年学生会干事工作总结
2014/11/07 职场文书
2014年学生党支部工作总结
2014/12/20 职场文书
导游词幽默开场白
2019/06/26 职场文书
css布局巧妙技巧之css三角示例的运用
2022/03/16 HTML / CSS
海弦WR-800F
2022/04/05 无线电