JavaScript客户端测试之旅

进行测试是很重要的。测试能让我们不费劲地扩展和重构我们的代码。许多开发者遵循测试驱动的开发流程。我相信写测试能让软件开发变得更有趣,并且通常会带来更好的代码。良好设计以及经过测试的系统更容易维护。

在过去的几年里,开发者开始把许多的应用逻辑放到浏览器中,我们也开始写越来越多的JavaScript代码。因为这种语言非常流行,开发者们开始创建用于改善JavaScript开发体验的工具。在这篇文章中,我们将谈论一些专门用于测试客户端JavaScript代码的工具。

测试设置

我们来讨论一下能够进行测试的工具类型,它们能让我们构建、分组以及运行我们的测试。

测试框架

框架包含一些函数如suite、describe、test 或 it。这些能让我们创建测试的分组,这些分组经常被称为套件(suit)。例如:

我们把应用逻辑分割成块,每个块都有自己的套件。该套件包括我们想在代码上运行的相关测试。流行的JavaScript测试框架有QUnitJasmine 或Mocha.

声明库

我们用声明库来做实际的检查。它们提供了易用的函数,如下面的例子:

有很多的模块可供我们使用。Node.js甚至就有一个内置的模块,也有一些开源的工具供我们选择,如ChaiExpect 或 should.js

应该提醒的是,一些测试框架拥有自己的断言库。

运行器

我们可以需要或者也可以不需要一个运行器(runner)。有些情况下单一的测试框架不能满足,因而我们需要在一个特定的上下文来运行测试。为了完成这件事,我们使用一个运行器。有些情况下这些工具被称为“spec runners”或“test runners.”。这些工具包装了我们的测试套件,并且在一个特殊的环境下运行。我们将会讲到这一点。

应用程序

我们需要一个待测试的应用程序。尽管这只是用于举例说明,但它不能太过简单。TODOMVC 看起来是个不错的选择。它基本上跟其他的TODO app一样,使用了许多不同的框架。我们用Backbone.js 的变种。这就是该应用程序的样子:

假设这是我们上个月在做的一个项目,并且计划下周发布。我们想确保它通过一些测试,也假设后端的已经测试过了。唯一的问题是客户端的JavaScript代码。在这个时候,由于应用程序已经完成了,我们最感兴趣的是它是否能正常地工作。它是一个TODO app,因此我们需要保证用户能够添加、删除以及编辑任务。

在浏览器中测试

我们需要对浏览器中运行的代码执行检查,因此在浏览器中进行测试是很合理的。我们将使用Mocha来作为框架。由于该框架不带断言库,我们将在项目中引用Chai。我们从todomvc.com 下载了app,并且浏览了文件:

这篇文章是关于测试的,因此我们不打算深入Backbone.js工作原理的细节。然而,这里介绍一下文件和目录的基本细节:

  • bower_components – 包含Backbone.js库、本地存储助手、jQueryUnderscore.js以及TODOMVC通用文件
  • js – 这个目录包含这个app的实际代码
  • bower.json – 定义项目的依赖
  • index.html – 包含HTML标签(模板)

在平常的开发流程中,我们打开一个浏览器、加载应用程序并且使用UI。我们在输入框敲入了一些文字新建了一个TODO,按Enter键然后任务显示在下面。通过点击小小的X标志来移除记录,通过双击TODO来编辑任务。我们有很多涉及不同鼠标事件的动作。

为了测试app,我们不想一遍遍重复上面的步骤。自动化测试可以帮到我们。我们将有效地运行这个应用程序,并且写能够与页面交互就像我们手动操作一样的代码。让我们创建一个tests_mocha文件夹存放我们的测试。

Mocha和Chai都可以通过npm来安装,npm来自Node.js。我们只需把它们添加到package.json文件,并且在同一个目录下运行npm install。

创建测试运行器

spec.js会包含我们的测试,TestRunner.html 是我们将要修改的副本。我们需要让应用程序跑起来,因此肯定会用到index.html的代码。在讨论改变之前先看一看原始文件是什么样的:

客户端的app会变得相当复杂,我们的一个app需要跑几个程序。文件末尾所有的<script>标签需要包好代表模板的<script>标签。<section> 也很重要,因此也保留它。

为了看测试的结果需要添加Mocha的样式,我们只测试JavaScript。因此TODOMVC的base.css可以移除掉,替换头部中的<link>标签:

样式去掉了但是标记仍然在那里。然而,我们不想让它可视,因此我们添加另外的CSS类来隐藏它:

有了这两处变化,我们仍然可以让项目运行和工作,但它的界面是不可见的。我们有一个空的页面来等待我们的测试。下面的代码在页面的底部运行,就在TODOMVC的脚本之后:

空的<div>被模板用来显示结果,在那之后我们添加Mocha和Chai。最后,我们运行测试。注意,我们的spec.js是在测试框架和声明库被初始化时在mocha.run()之前添加的。实际上,浏览器会:

  • 导入Mocha的CSS;
  • 导入TODOMVC的文件并且运行应用程序;
  • 导入Mocha和Chai;
  • 导入我们的spec.js;
  • 运行测试

编写测试

准备就绪,让我们开始写测试。之前我们提到过要检查三件事情:用户能否添加、删除、编辑待完成任务。

测试从调用describe 函数开始,把我们的检查放在一个测试套件里。setText 函数是用来改变输入框的值并模拟按下Enter 键。

大多数测试框架允许我们在测试运行之前执行逻辑,这就是我们用before 函数的原因。在本例中,我们需要清空保存在localStorage 中的数据,因为之后我们期待看到列表中待完成任务的具体数目。

接下来的it 调用表示添加、删除、编辑的三种操作。注意我们用的$ (jQuery)和expect 都是全局函数。

运行测试

完成这段代码,就可以用我们最喜欢的浏览器打开TestRunner.html,并且结果显示如下:

我们现在能保证我们的项目提供了所需的功能。我们有一个测试并且它是有点自动化的。之所以说“有点”是因为测试在浏览器中运行,并且我们仍然需要手工打开TestRunner.html。

这就是使用这种的问题之一。我们不能强迫开发者一直在浏览器中运行测试。测试过程应该是配置或提交过程的一部分。为了达到这个目标,我们需要将测试移到终端。

使用PhantomJS进行测试

下面我们要解决的一个问题是,找到一个能在终端运行的浏览器。我们需要一个浏览器,因为Backbone.js需要渲染UI并且我们要跟它交互。有一种被称为没有界面的(headless) 特殊的浏览器,它并没有一个可视化界面。

我们通过代码来控制它。但是,它们跟真实的浏览器功能一模一样。最流行的一款是PhantomJS。尝试一下看它怎样处理我们的测试。

PhantomJS以可执行文件的形式发布。换句话说,当成功安装之后我们有一个命令变量phantomjs 。它适用于几乎每一个操作系统。同该浏览器一同使用,我们将使用Node.js和它的npm。

在终端运行测试

假设我们已经安装好了PhantomJS,下一步是在终端运行我们的Mocha测试。实际过程中,我们需要在浏览器中加载TestRunner.html,检查来自Mocha框架的结果。我们可以手动操作。但是为了节省时间,我们使用Node.js中的mocha-phantomjs 模块。

快速运行npm install -g mocha-phantomjs会让mocha-phantomjs可在控制台中使用。直接将tests_mocha文件复制到新的目录tests_mocha-phantomjs下。我们要做的唯一的变动就是将

变为:

模块从测试框架获得回应,并将它发送到终端。这就是结果应该有的样子:

现在我们的检查在终端里运行,我们能添加mocha-phantomjs调用到我们的持续整合设置。

PhantomJS很好,但是它在单一的浏览器中运行我们的代码。如果我们在不同的浏览器中测试代码呢?特别是针对客户端的JavaScript,我们应该保证我们的应用程序能在不同的环境中工作。并且不止如此,我们想在真实的浏览器中测试。Karma 项目能提供这个功能。

使用Karma作为测试运行器

与mocha-phantomjs类似,Karma 以Node.js模块的形式发布。但是,这一次我们不仅需要一个模块,还需要其它几个模块。因此,让我们按照上述的想法,将tests_mocha拷贝到新的文件tests_karma里。package.json文件应当是这个样子:

除了上面的包,我们还需要karma-cli。在运行npm install(这会安装上面列出的依赖)之后再运行npm install -g karma-cli。 karma-cli 有karma 命令,我们可以调用它来在终端运行测试运行器。

使用KARMA的问题

我在过去几个月看了很多有关Karma的东西,并且真的想尝试它。但是,我发现它被设计专门用于单元测试,而不是集成测试。

运行器的命令行工具接受JSON对象格式的配置,有一些选项,但是没有一个接受HTML文件。我们可以发送JavaScript文件到浏览器,但是我们不能定义要加载的HTML。

有一个Karma使用的上下文模板,它的所有功能是注入JavaScript代码和测试。为了使用方便,这是不够的。我们有HTML标记及<script>中的模板。在运行应用程序之前它们应该已经被加载到页面中了。

解决方案

我所做的是fork这个项目,并且在配置中增加了一个选项。这可以让我们设置上下文模板,这也是我为什么在package.json 文件中添加一个URL。

保持spec.js不变,把TestRunner.html变为:

需要的<section>和 <footer>都包含在模板中了,最后的代码是Karma用于构建最终文档及运行测试。

配置

我提到框架使用了一个配置文件。使用下面的内容创建一个tests_karma/karma.conf.js文件:

在这个配置里,我们列出了要注入到页面的JavaScript文件,contextFile 设置是我之前讨论过的变化。注意,我们有两种浏览器来运行测试。

运行KARMA

最后一步是在终端运行karma start ./karma.conf.js –single-run。结果如下:

在这一部分的开头karma-phantomjs-launcher和karma-chrome-launcher。该框架使用两个模块来运行我们指定的浏览器。

因此,我们尝试在浏览器中运行测试,但是这个方法不能扩展。我们通过mocha-phantomjs让其在终端运行,但是那意味只在一个浏览器中测试。第三种尝试是使用Karma作为运行器,开启两个不同的浏览器,其中一个不是没有界面的(headless)浏览器。最后一个步骤有点复杂,包括一些模块及一个框架补丁。让我们尝试另一个运行器——DalekJS

使用DalekJS测试

DalekJS使用一种不同的方法,它不需要测试框架或者断言,已经自带了。

安装非常简单。我们再一次需要Node.js和npm ,因为这个工具是以Node.js的包发布的。跟Karma一样,我们也需要命令行客户端,以及DalekJS框架本身。

我们创建另一个tests_dalekjs 文件夹,包含package.json 文件:

当我们安装好了两个模块,就可以进行测试了。

编写测试

好消息是我们不需要接触HTML,只需把index.html的文件路径拷贝到tests_dalekjs/TestRunner.html文件中,剩下的步骤都一样。

因为DalekJS有自己的语法,我们不能像上面的测试使用spec.js文件。下面是使用DalekJS API所写的三种操作:

再一次,我们有一个帮助的方法来设定输入域的值,并且触发Enter 按钮。我们导出的对象包含TODO的添加、删除、编辑。像这样设计API是很友好的,因为我们仅通过看方法的执行来知道正在发生什么。

有一个比较麻烦的方法是执行。它接受一个在浏览器上下文下执行的函数。

运行测试

我们使用dalek ./tests_dalekjs/spec.js运行测试,结果:

我们应该记住,DalekJS跟Karma一样,能够在Chrome、IE、Firefox 和 Safari下运行。相当多的现代浏览器是支持的,我们所要做的是安装额外的模块,如 dalek-browser-chrome。更多有关支持的浏览器,点这里

Atomus——测试的另一种工具

Atomus 是我工作时使用的工具,上面所有的功能选项都很棒。它们中的大多数经过了大型社区的良好测试。然而,在我看来,它们并非是理想的。

我们能覆盖用户的全部过程是很好的,但是通常情况下我们只需要测试应用程序的某一个部分。如果我们只想测试Backbone.js应用的特定视图呢?使用DalekJS这是很困难的,使用Karma可行但是有点麻烦。模块mocha_phantomjs跟工作方案很接近但是也有一些局限。

当我们进行单元测试时,我们意识到我们所做的是DOM模拟。在大多数情形下我们对UI不感兴趣,而对它的行为感兴趣。此时我们找到了jsdom,它是WHATWG和HTML标准的Javascript的实现。

我们创建了一些测试,发现它工作得非常好。它支持DOM操作和DOM事件调度/监听。甚至支持Ajax请求。Atomus是jadom的包装器,提供一个强健和友好的API。

创建测试

让我们以使用Atomus进行的TODOMVC测试来结束这篇文章。我们再一次用到Mocha 和Chai。在新目录tests_atomus创建package.json,定义依赖:

TestRunner.html跟原始的index.html文件一样——唯一引入的外部JS文件应该移除掉,因为spec,js 中已经引入过了。文件以下面的代码开始:

我们使用文件系统API来读取TestRunner.html的内容。browser 变量代表Atomus API。Atomus库会自动注入jQuery,因此$可以作为快捷符号。第一个测试是这样:

我们在这里初始化虚拟浏览器、定义外部的js文件、设定HTML标记的页面。ready函数接收一个回调,但页面和资源全部被加载完成之后会触发。我们接收一个window对象,跟实际浏览器中的window对象一样。我们可以认为变量是指向浏览器的API的,当然也是指向全局作用域的。

Atomus有一个keypressed 和clicked 的方法模拟用户交互。我们也可以使用流行的jQuery方法来获得同样的结果,但是这些内置的方法可能会带来一些bugs。

既然我们有了browser 变量,我们能继续删除和编辑TODO了。

运行测试

为了运行测试我们不需要命令行客户端,只需要键入mocha ./spec.js ,结果是:

注意,当我们使用Atomus时主要的事情不是运行器或者没有界面的浏览器——它是测试框架。就是它在驱动测试,Atomus只是一个帮助工具。

在我们的案例中,它帮助我们覆盖不同层次的客户端架构测试。我们有简单UI元素的测试,同时使用同样的工具来整合测试。

为了完成一些有趣的事情,我们看下面的代码:

在复杂的环境中,当应用程序的UI跟后端有通信时,我们需要模拟HTTP请求。在我们的案例中,我们想要涵盖一个完整的使用过程,但是这实际上变得很复杂,因为我们想要进行单独的测试。因此,我们模拟传统的XMLHttpRequest 对象,现在开发者可以通过不同的响应链接到URL了。

结论

测试是很有趣的——特别是当我们有这么多的工具可供使用的时候。不管我们有着哪种类型的应用,我们应该明确的是有一种测试它的方法。我希望这篇文章能够帮助你找到合适你的项目的正确工具。

1 1 收藏 评论

关于作者:XfLoops

新浪微博:@XfLoops 个人主页 · 我的文章 · 10

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部