十、应用的性能测试

在本书中,我们介绍了在项目生命周期的不同阶段提高 JavaScript 应用性能的各种方法。 这包括从在项目生命周期的各个阶段选择合适的编辑器,在部署之前结合 JavaScript 检查器来帮助验证 JavaScript,到使用构建系统,以及创建部署包或构建将最终代码从开发人员友好的代码库分离出来。

制作高性能 JavaScript 的真正秘密不在于我们头脑中有多少 JavaScript 知识,而在于了解语言本身的关键“痛点”; 其中一些难点是for循环、对象创建、不包含严格运算符、计时器等。 此外,这个类别还包括合并这些工具,以便在部署代码之前更好地检查代码。

就像所有主要的 web 应用项目一样,这里总是有某种形式的飞行前检查,即 web 应用上线前的最终待办事项列表。 如果我们将本书中提到的工具结合到这一点,我们的 JavaScript 应该足够牢固,可以用于部署。 但在这里,我们将更进一步。

在这一章中,我们将看一看Jasmine,这是一个 JavaScript 测试框架,它允许我们以我们还没有意识到的方式测试我们的代码。 与过去的测试工具(如 JSLint)不同,这些测试将依赖于应用的属性类型,以及我们还没有涉及到的概念:JavaScript 中的单元测试。

简而言之,我们将涵盖以下主题:

  • 什么是 JavaScript 的单元测试?
  • 使用 Jasmine 进行单元测试

什么是 JavaScript 单元测试?

简单地说,单元测试是一种应用框架或工具集,设计用于测试 JavaScript 或任何其他编程语言的代码,以特定的方式对任何应用都是独特的。 单元测试通常包括标准检测程序中不存在的错误检查。 它们被设计用来检查特定于应用的错误。 在其他编程语言中,单元测试通常用于检查项目的类和模型,并确保应用有效而正确地运行。

现在,JavaScript 和单元测试实践从来没有很好地联系在一起,这主要是由于 JavaScript 的动态特性。 阻碍它们关联的一些因素包括开发人员在不知情的情况下造成的许多错误,将错误的值传递给不应该具有特定变量类型的变量,在应用的对象属性需要数字时分配字符串,等等。

然而,对于使用 JavaScript 的客户端应用来说,无论它们是在 web 浏览器中还是在移动应用的 web 视图中,测试变得越来越有必要。 现在市面上有许多为 JavaScript 测试而设计的框架,但在这里,我将专门介绍一个名为 Jasmine 的框架。 请记住,还有其他测试框架,如 Mocha 或 QUnit,但我们将介绍 Jasmine,因为它不需要第三方框架来运行。

使用 Jasmine 进行单元测试

Jasmine 是一个 JavaScript 单元测试框架; 它允许我们编写 JavaScript,而不依赖于 jQuery 等外部库。 这对于需要占用非常小内存的应用很有帮助,比如我们在 iOS 中的 JavaScript 应用,我们将在第 9 章优化 iOS 混合应用的 JavaScript中讨论。 它还将代码限制为我们编写的代码,并且没有由于其他供应商库的当前构建中的框架而导致的 bug。

安装和配置

茉莉可按多种方式安装; 我们可以使用 node 包管理器或 NPM,类似于我们在第 3 章中构建 Gulp.js 构建系统的方法。 但是,为了熟悉测试,我们将下载框架的独立版本。 我将使用 2.1.3 版本,这是框架的最新稳定版本,可以在https://github.com/jasmine/jasmine/releases上找到。 点击 Jasmine 框架 Github 页面上的绿色.zip文件按钮下载:

Installation and configuration

一旦我们下载茉莉的独立版本,我们可以检查它是否工作; 独立版本包含了一些用单元测试设置的 JavaScript 示例。 要在 Jasmine 中运行一组单元测试,我们需要构建一个SpecRunner页面。 SpecRunner是一个特定于 jasmine 的 HTML 页面,显示单元测试结果。 如果我们在浏览器中打开独立版本SpecRunner.html文件,我们应该会看到示例测试结果,显示所有已经通过的测试,如下面的截图所示:

Installation and configuration

在设置测试之前,我们需要测试一些代码。 我已经创建了一些 JavaScript,它是面向对象的,严重依赖于特定的 JavaScript 类型,如数字和布尔值,它们在整个应用中使用。 该应用是一个非常简单的银行应用,它将客户数据返回到一个简单的 HTML 页面,但它的结构足够类似于一个大型应用。 我们将使用 Jasmine 检查类型,确保传入的数据是有效的,并验证应用输出的是客户数据。

评审项目代码库

我们将在项目中使用以下代码示例。 花点时间看看这里显示的代码。 与往常一样,本书的所有代码示例也可以在 Packt Publishing 的网站上找到。

Reviewing the project code base

我们有相当多的代码在这里测试,但没有必要担心! 在开始使用 Jasmine 之前,让我们慢慢地回顾一下。 在第 1 行到第 7 行,我们有一个性别类型的 JavaScript 枚举,允许我们为客户类型预定义值。 在本例中,值为MaleFemaleAlien。 从第 10 行开始是我们的BankDB对象(也被认为是 JavaScript 类); 这不是一个真正的数据库,但它可以很好地连接到一个真正的应用。

BankDB函数是一个基于实例的对象,这意味着它需要一个特定类型的参数才能起作用,我们可以在第 56 行找到newCustomer。 这个 JavaScript 对象包含一个 JavaScript 对象表示法,它将值赋给一个新的客户条目。 可以将其看作是一个职员在使用系统时返回的一个 JSON。

最后,在第 66 行到第 72 行,我们使用该用户的数据创建请求,然后将数据添加到嵌入的 web 页面的document.body语句中,使用一些简单的样式和格式。

在开始编写测试之前,让我们在一个自包含的页面中看看这个。 我将把它添加到一个空的 HTML 页面,就在结束的body标签之前。 让我们打开页面并查看结果,如下面的截图所示:

Reviewing the project code base

正如我们所看到的,我们的应用显示了所有正确的信息,除了客户的名字,客户的名字显示为Mr. e,而不是Mr. Leonard Adams,正如我们的10_01.js文件的第 58 行所示。 另外,请注意,在我们的 Chrome开发者工具选项中,我们没有接收到任何错误,也没有看到太多的性能延迟。 然而,我们确实从客户名称的输出中知道有问题。 为了纠正这个问题,我们将对应用进行单元测试。

为编写测试而审查应用的规格

在编写单元测试时,需要明确定义编写测试的指令; 对于上一个屏幕截图中显示的代码示例,我们希望确保我们的测试遵循一些规则,为了帮助我们编写这些测试,我们将在下表中列出的规则与代码一起使用。

将下面的列表作为应用规范或文档,应用应该基于此构建。 让我们看看这个表格,看看我们的代码应该如何处理正在使用的数据:

|

测试数量

|

测试描述

| | --- | --- | | 测试# 1 | 新客户数据测试:Customer's ID should be a number. | | 测试# 2 | 新客户数据测试:Customer's name should be in an array object, (ex ['FirstName', 'LastName']). | | 测试# 3 | 新客户数据测试:Customer's bank balance should be a number. | | 测试# 4 | 新客户数据测试:Customer's city name should be a string. | | 测试# 5 | 新客户数据测试:Customer's gender should be a number. | | 测试# 6 | 新客户数据测试:Customer's marriage status is a boolean. |

根据这个列表,我们需要我们的数据值通过这六个测试,以确保 JavaScript 应用正常工作。 为此,我们将使用 Jasmine 编写一个规范。 在 Jasmine 框架中,规格文件只是一个 JavaScript 文件,将待测试的 JavaScript 加载到包含 Jasmine 测试框架和待测试文件的 HTML 页面中。 这里,我们可以看到合并后的页面是什么样的; 在基于茉莉花的测试中,它通常被称为SpecRunner页:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Jasmine Spec Runner v2.1.3</title>

    <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.1.3/jasmine_favicon.png">
    <link rel="stylesheet" href="lib/jasmine-2.1.3/jasmine.css">

    <script src="lib/jasmine-2.1.3/jasmine.js"></script>
    <script src="lib/jasmine-2.1.3/jasmine-html.js"></script>
    <script src="lib/jasmine-2.1.3/boot.js"></script>

    <!-- include source files here... -->
    <script src="src/Chapter_10_01.js"></script>

    <!-- include spec files here... -->
    <script src="spec/Chapter_10_01Spec.js"></script>

  </head>

  <body>
  </body>
</html>

在这里,我们可以看到SpecRunner.html页面,请注意,我们有茉莉花框架加载第一个头部标签,其次是我们的测试脚本显示在这一章叫做Chapter_10_01.js,然后跟着我们的规范文件命名为Chapter_10_01_Spec.js一致性。

注意,如果我们打开我们的 Chrome开发工具在我们的SpecRunner.html页面,我们可以看到一些错误来自我们的10_01.js文件,其中我们添加了我们的客户数据document.body声明。 使用 DOM 的 JavaScript 可能会导致 Jasmine 和其他 JavaScript 测试框架出现问题,所以要确保使用特定于应用的代码而不是用户界面代码进行测试。

使用 Jasmine 编写测试

在 Jasmine 中,有三个我们需要知道的测试框架的关键词。 第一个是describe; describe就像类的测试。 它将我们的测试分组在一个容器中,以便稍后引用。 在前面的应用规范列表中,我们可以使用New Customer data test作为描述值。

第二个关键字是it; it是一个 Jasmine 函数,它接受两个参数,一个字符串,我们使用它作为测试描述。 例如,一个it测试可以包含Customer's ID should be a number这样的描述。 这将告诉复审测试的用户我们到底在测试什么。 另一个参数是一个函数,我们可以在其中注入代码或在需要时设置代码。 记住,所有这些都是在同一个页面中运行的,所以如果我们想要更改测试的任何变量或原型,我们可以在运行测试之前在这个函数中这样做。 请记住,在编写测试时,我们不需要修改代码来正确地进行测试; 只有在我们没有代码示例供检查的情况下才会这样做。

最后一个要记住的关键词是expect; expect是一个特定于 Jasmine 的函数,它接受一个值,并将其与某些其他值进行比较。 在 Jasmine 中,这是使用toEqual函数完成的,toEqual函数是expect函数的一部分。 把每个测试想成这样可读:We expect the typeof newCustomer.customerID to equal a number。 仔细想想,这很简单,但是规范文件是什么样子的呢? 好吧,如果我们看看下面的截图,我们可以看到我们的Chapter_10_01Spec.js文件,每个测试都为 Jasmine 准备好了:

Writing tests using Jasmine

在这里,我们可以看到我们的测试是如何编写的; 在第 2 行,我们有我们的describe关键字包装我们的测试在一个容器中,我们应该有一个更大的测试文件。 我们文档规范中的所有测试都可以通过每个it关键字找到; test number 1 在第 4 行,在第 5 行,我们有第一个测试的expect关键字检查newCustomers.CustomerID类型,我们期望一个number

注意,正在比较的类型使用的是字符串而不是数字,就像在控制台中预期的一样。 这是,因为 JavaScript 用于返回变量或属性类型的关键字typeof使用字符串返回类型名; 为了匹配它,我们这里也使用了带类型名的字符串。

在随后的几行中,我们可以看到我们已经为每个其他测试添加了相同的比较风格的其余测试。 做完这些,让我们打开SpecRunner.html页面; 我们可以看到我们的测试是如何做的规格列表视图如下截图:

Writing tests using Jasmine

呵! 这里有三个错误,这一点都不好。 在这里,我们期望出现一个客户名称没有正确显示的单一错误。 但是,我们的单元测试发现,我们的应用规范没有按照编写时的要求执行。 在 Jasmine 框架中,这种页面布局非常常见; 在初始加载时,您将看到一个完整的错误列表。 如果您希望看到所有通过和失败的测试的列表,我们可以点击顶部的Spec list,我们将看到完整的列表,如前面的截图所示。

在这里失败的测试在浏览器上显示为红色,通过的测试显示为绿色。 您还可以在Failures视图和Spec List视图中看到绿色圆圈和红色 Xs,表示通过和失败的测试数量。

修复代码

随着我们的测试代码现在工作,我们可以修改它,以确保正常工作。 为此,我们需要更新10_01.js文件和newCustomer数据,这些数据位于10_01.js文件的第 56 行到第 63 行。 让我们回顾一下我们的样本客户数据出了什么问题:

  • 第一个失败的测试是 2,它要求将客户的名称创建为对象数组,将第一个名称作为数组项,最后一个名称作为对象数组中的第二个项
  • 第二个失败的是测试 3,它要求customerBalance是一种数字类型
  • 第三个错误是测试 6,它要求客户的婚姻状态为布尔值而不是字符串

更新我们的newCustomer数据; 你可以看到,我已经做了以下截图:

Fixing our code

一旦我们在我们的10_01.js文件中更新了newCustomer信息,我们应该能够重新运行 Jasmine 并重新测试我们的代码示例。 如果所有测试都通过了,我们将看到默认的Spec List以绿色显示所有结果; 让我们按照如下截图重新打开页面,看看我们的测试是否通过:

Fixing our code

很好,所有六个规格都通过了! 伟大的工作! 通过确保应用的所有数据都使用正确的类型,我们还可以确保 JavaScript 应用不仅执行良好,而且执行的精度也很高,因为它是被使用的。

当应用偏离开发人员的设计时,就会导致性能问题,影响应用的整体稳定性。 在 Jasmine 中,我们可以看到测试的完成时间; 注意,最终测试的性能要比有错误的测试快得多。 在下面的截图中,我们有我们的最终应用页面没有错误,如 Chrome 中的开发者工具选项所示:

Fixing our code

最后一个需要注意的事实是 JavaScript 开发人员可以使用的不同方法。 一种是测试驱动开发(TDD)方法,我们在编写应用代码之前编写测试。 许多 JavaScript 开发人员测试应用的另一种方法叫做行为驱动开发(BDD)方法。 这是通过先编写应用代码,然后与应用进行交互来实现的,其中包括打开一个弹出窗口,并确认代码按照预期运行。

这两种方法对于构建应用来说都是有效的方法,但对于 JavaScript 应用来说,它需要使用一些必须是准确的数据,TDD 是最好的方法!

小结

在本章中,我们介绍了 JavaScript 应用单元测试的基础知识。 我们引入了 Jasmine,这是一个行为驱动的 JavaScript 单元测试框架。 我们一起创建了一个真实的应用,它没有技术错误,但会导致应用问题。

我们回顾了如何阅读和编写应用规范,以及如何在 Jasmine 中使用应用规范编写测试。然后,我们根据代码运行测试,并快速更新客户数据,以反映规范,让单元测试通过。 最后,我们了解到,对代码进行单元测试可以提高 JavaScript 的性能,并将应用的风险降至最低。