内容


精通 MEAN

测试 MEAN 堆栈

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 精通 MEAN

敬请期待该系列的后续内容。

此内容是该系列的一部分:精通 MEAN

敬请期待该系列的后续内容。

User Group List and Information (UGLI) 应用程序自您启动它以来已进行了许多改进。您将本地数据存储在应用程序中,通过 RESTful Web 服务拉入远程数据。该应用程序支持一种移动就绪的 响应式 Web 设计,而且它 进行了语义标记,以便充分利用搜索引擎优化 (SEO)。对于 身份验证,用户可以创建一个本地的专用帐户,或者(通过 OAuth)重用一个存储在别处的现有帐户。

但是,没有一个坚固的测试套件作为您的安全网,您可能无法放心地将 UGLI 应用程序部署到生产中,对吧?是的。这在专业上是不负责任的。我同意 Neal Ford(作家和国际演讲家)将测试称为 “软件开发的工程严谨度检查”。只要我开始接洽新客户,就会首先查看他们的测试套件,然后再查看其他任何部分 — 甚至他们的设计文档。他们的测试的质量、数量和全面性与其软件开发流程的成熟度具有直接的关联。健康、经过积极维护的测试套件是项目总体健康的首要保证。类似地,任何鼓励可测试性的框架我都会优先考虑。AngularJS 是测试人员编写的,我不得不考虑另一种更容易测试的现代 Web 框架。MEAN.JS 堆栈扩展了开箱即用的可测试性,以包含服务器端逻辑的测试。

AngularJS 是测试人员编写的,我不得不考虑另一种更容易测试的现代 Web 框架。

本系列 的开头部分,我介绍了 MEAN 堆栈的基本构建块 — 组成您应用程序的生产组件的多个松耦合的小组件。对于将用于测试您应用程序并使其适合生产的各种框架和库,是时候对它们做同样的事了。我将介绍 Karma:一个可插入的测试运行器,它使得跨任意多个真实 Web 浏览器(包括智能电话、平板电脑和智能电视)运行在任何测试框架中编写的测试,并以各种不同格式返回结果变得非常容易。在此过程中,您将使用 Jasmine 执行客户端测试,使用 Mocha 执行服务器端测试,使用 istanbul 执行代码覆盖测试。

运行您的测试

因为您已在使用 MEAN.JS 框架标配的 Yeoman 生成器,所以您已有多个已生成的测试。键入 grunt test 来运行它们。您会看到类似清单 1 的结果。

清单 1. 运行生成的测试
$ grunt test
Running "env:test" (env) task

Running "mochaTest:src" (mochaTest) task

 Application loaded using the "test" environment configuration

Running "karma:unit" (karma) task
INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Mac OS X)]: Connected on socket 6zkU-H6qx_m2J6lY4zJ8 with id 51669923
PhantomJS 1.9.8 (Mac OS X): Executed 18 of 18 SUCCESS (0.016 secs / 0.093 secs)

Done, without errors.

如果遇到错误或警告,不用担心;这些测试在最初实现时是为匹配各种模型和控制器而搭建的。如果您更改正在测试的代码 (CUT) 而没有更新相应的测试,那么您可能遇到了错误。

每次单元测试失败都让我很兴奋。单元测试就像您代码库的断路器。在您家里,您将断路器放在电网与您昂贵的个人电子设备之间。这样,当一个可能有损害的电涌通过电线传入时,您只会损失一个 35 美分的断路器,而不是一台价值 3,500 美元的笔记本电脑。类似地,单元测试的每次破坏都是您看到的一个错误,而用户不会看到这些。

如果可以的话,应该花费一些时间来修复您被破坏的测试。一个常见的错误来源是测试对已删除或更改的字段名称的依赖性。服务器端测试在 app/tests 中。客户端测试存储在每个 public/module 的 test 目录中。如果无法立即看到您测试中的错误来源,不要删除测试;可将它暂时移出到该目录外。

在能够运行干净的测试后,是时候分解该流程了。

理解 Grunt test 任务

在您键入 grunt test 时,希望您会问自己,“嗯,我想知道 Grunt 如何运行这些测试。”如您已经知道的,Grunt 运行您的构建脚本。在文本编辑器中打开 gruntfile.js 并一直滚动到文件底部。您可以看到 test 任务正在注册:

// Test task.
grunt.registerTask('test', ['env:test', 'mochaTest', 'karma:unit']);

grunt.registerTask 的第一个参数是任务的名称 — 在本例中为 test。下一个参数是一个依赖任务数组。test 任务首先设置特定于测试环境的值,然后运行所有在 Mocha 中编写的服务器端测试,最后通过 Karma 启动客户端测试。

在 gruntfile.js 中稍微向上滚动,直到找到 env 任务:

env: {
    test: {
        NODE_ENV: 'test'
    }
},

此任务仅用于将 NODE_ENV 变量设置为 test。回想一下,此变量帮助 Grunt 确定哪些特定于环境的设置(在本例中位于 config/env/test.js)将会与 config/env/all.js 中的通用设置合并。

如果在文本编辑器中查看 config/env/test.js(如清单 2 所示),就会看到一个自定义的 MongoDB 连接字符串,以及针对各种 OAuth 提供程序的所有 Passport 设置的挂钩(hooks):

清单 2. config/env/test.js
'use strict';

module.exports = {
    db: 'mongodb://localhost/test-test',
    port: 3001,
    app: {
        title: 'Test - Test Environment'
    },
    facebook: {
        clientID: process.env.FACEBOOK_ID || 'APP_ID',
        clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/facebook/callback'
    },
    google: {
        clientID: process.env.GOOGLE_ID || 'APP_ID',
        clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/google/callback'
        },
    // snip
};

这一节是您将 Passport 指向 Meetup 身份验证战略的一个模拟实现的理想位置。因此,在测试运行期间,您就不需要依靠实际用户进行设置,并向 Meetup.com 发送实际的 OAuth 请求。

完成测试环境配置后,Grunt 将会运行您在 Mocha 中编写的所有服务器端测试。以下是 mochaTest 任务:

mochaTest: {
    src: watchFiles.mochaTests,
    options: {
        reporter: 'spec',
        require: 'server.js'
    }
},

为什么是 Mocha 中编写的服务器端测试,而不是 Jasmine?Mocha 的成熟度、可扩展性和插件使它成为了我最喜爱的测试框架之一。Mocha 是测试 Express 路由、控制器和 MongoDB 交互等方面的可靠的选择。尽管 Mocha 可以轻松地在 Node.js 和浏览器中运行测试,但 AngularJS 团队更喜欢使用 Jasmine 来执行浏览器中的测试。Jasmine 针对客户端测试进行了更多优化,所以 MEAN.JS 开发人员采取了一种一流的方法,并选择了一个强大的服务器端测试框架来测试服务器端,选择了一个强大的客户端测试框架来测试客户端。您应该习惯交替使用两种框架来使用您首选的测试工具。

因为服务器端测试(根据定义)不在浏览器中运行,所以 Mocha 测试不会由 Karma 启动。Jasmine 测试(Grunt test 依赖项的最后部分)由 karma 任务触发:

karma: {
    unit: {
        configFile: 'karma.conf.js'
    }
}

在继续分解 karma.conf.js 文件之前,在文本编辑器中打开 package.json。除了 dependencies 块中列出的运行时模块之外,您还可以看到 devDependencies开发人员依赖项 的缩写)块中列出的多个构建时依赖项。具体地讲,这个代码块就是在您键入 npm install 时,声明和安装与 Mocha 和 Karma 关联的 Grunt 插件的地方:

  "devDependencies": {
    "grunt-env": "~0.4.1",
    "grunt-mocha-test": "~0.10.0",
    "grunt-karma": "~0.8.2",
    "load-grunt-tasks": "~0.4.0",

    // snip
  }

Karma 简介

Karma 是我知道的惟一一个受一篇 硕士论文 支持的测试运行器。Karma 背后的思想的一种更简洁的版本描述是一段对项目的任务陈述:

思想应该很简单。我们信任测试,所以我们希望让它尽可能简单。

仅此而已。Karma 允许您在您选定的框架中编写测试。无论您更喜欢 QUnit 的测试驱动开发 (TDD) 风格还是 Jasmine 的行为驱动开发 (BDD) 风格,Karma 都能正常运行采用任何风格编写的测试。(如果您更喜欢使用单个测试框架来编写服务器端和客户端测试,Karma 还为 Mocha 提供了一流的支持。)

经验丰富的 Web 开发人员知道跨各种不同的浏览器测试他们的应用程序有多重要。核心 JavaScript 语言在各种浏览器中惊人地一致,但文档对象模型 (DOM) 操作和发出 Ajax 请求的方式远远没有标准化。jQuery 和 AngularJS 等主流库出色地克服了浏览器不兼容性,但这不应该让您错误地感到自鸣得意。一次测试胜过一千条意见,有证据证明您的应用程序在特定浏览器中按预期运行,远胜于简单地假设它将按预期运行。

Karma 提供了多个插件,您可以使用它们按需启动一个真实的浏览器,运行完整的测试套件,然后在完成后关闭浏览器。该功能对在您选择的浏览器本地运行测试很方便,但如果测试由一个无头 (headless) 持续集成服务器(比如 Jenkins、Hudson 或 Strider)启动,您的选择可能会很受限制。

幸运的是,您可以启动一个长期运行的 Karma 服务器并捕获 远程设备上的浏览器。捕获一个浏览器很简单,只需在浏览器中访问您的 Karma 服务器的 URL。如果浏览器支持 Web 套接字(caniuse.com 表明支持每个主流的现代浏览器),Karma 服务器将与该设备保持一个长期运行的、持久的连接。随着新测试添加到套件中,Karma 服务器将通过线路将它们一次传输到远程浏览器,运行它们,然后返回结果。

但是,如果您不能量化结果,运行测试套件有什么用?Karma 提供了针对一些不同报告程序的插件。报告程序可以简单到为每个通过的测试在命令行上打印一个点。或者报告程序可生成完全格式化的 HTML,或者发布可转换为您选择的输出的原始的 JUnit 兼容 XML。

测试框架、浏览器启动器和结果报告程序都是在 karma.conf.js 文件中定义的。

理解 karma.conf.js

在文本编辑器中打开 karma.conf.js,如清单 3 所示。在该文件中,您会找到清晰标识的 frameworksfilesreportersbrowsers 设置。

清单 3. karma.conf.js
'use strict';

/**
 * Module dependencies.
 */
var applicationConfiguration = require('./config/config');

// Karma configuration
module.exports = function(config) {
    config.set({
        // Frameworks to use
        frameworks: ['jasmine'],

        // List of files / patterns to load in the browser
        files: applicationConfiguration.assets.lib.js.concat(applicationConfiguration.assets.js,
        applicationConfiguration.assets.tests),

        // Test results reporter to use
        // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
        //reporters: ['progress'],
        reporters: ['progress'],

        // Web server port
        port: 9876,

        // Enable / disable colors in the output (reporters and logs)
        colors: true,

        // Level of logging
        // Possible values: config.LOG_DISABLE || config.LOG_ERROR ||
           config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // Enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // Start these browsers, currently available:
        // - Chrome
        // - ChromeCanary
        // - Firefox
        // - Opera
        // - Safari (only Mac)
        // - PhantomJS
        // - IE (only Windows)
        browsers: ['PhantomJS'],

        // If browser does not capture in given timeout [ms], kill it
        captureTimeout: 60000,

        // Continuous Integration mode
        // If true, it capture browsers, run tests and exit
        singleRun: true
    });
};

返回查看 package.json。在该文件中,您可以在 devDependencies 块中找到各个 Karma 插件的相应条目:

  "devDependencies": {
    // snip

    "karma": "~0.12.0",
    "karma-jasmine": "~0.2.1",
    "karma-coverage": "~0.2.0",
    "karma-chrome-launcher": "~0.1.2",
    "karma-firefox-launcher": "~0.1.3",
    "karma-phantomjs-launcher": "~0.1.2"
  }

因为所有搭建的客户端测试都是在 Jasmine 中编写的,所以推荐保留 frameworks 数组原封不动。但您在本节后面将会看到,您可以轻松地随意添加和删除浏览器。

PhantomJS 简介

如果您是 Web 开发人员,但不熟悉 PhantomJS 浏览器,那么您需要补上这一课。PhantomJS 是最适合 Web 测试人员的工具之一。

用户很容易受到欺骗,将 Web 浏览器视为某个具有熟悉的品牌名称的庞大应用程序:Firefox、Chrome、Safari、Opera 和 Internet Explorer。这些品牌名称只是描述一个特定的技术集合的方便方式,这些技术包括渲染引擎(针对 HTML 和 CSS)、脚本引擎(针对 JavaScript)和插件子系统。

如果您是一位经验丰富的 MEAN 开发人员,您一定非常熟悉从来浏览器中分离出组件并无头地运行它们。您应该能够自在地运行一个无头的渲染包来进行测试。

将浏览器视为一个松散的渲染工具包和脚本引擎集合时,您将拥有全新的理解。例如,在上世纪 90 年代发布 2.0 版时,Netscape Navigator 浏览器拥有超过 90% 的市场份额。IE 在几年后接过了市场领导地位。但在最近几年,一个渲染包( WebKit )而不是浏览器获得了大部分市场份额。因为在最近(参阅 当 WebKit 遇上 Blink 边栏),WebKit 支持着 Safari、Mobile Safari、Chrome、Android 浏览器、BlackBerry 浏览器、Kindle 设备、PlayStation、三星智能电视、LG 智能电视、松下智能电视等。尽管这些应用程序和设备由不同的公司和项目组装,但它们使用一个通用的渲染包来显示 HTML 和使用 CSS 设置其样式。

那么,这与 PhantomJS 有什么关系?PhantomJS 的网站告诉我们:

PhantomJS 是一个可使用 JavaScript API 编写脚本的无头 WebKit。

无头服务不需要监视器或 GUI。这听起来非常适合在无监视器的持续集成服务器撒谎能够运行基于浏览器的单元测试,对吧?(SlimerJS 项目提供了类似的功能:运行一个无头的 Gecko 渲染包来测试在 Firefox 浏览器中发生的页面渲染。)

如果您是一位经验丰富的 MEAN 开发人员,您一定非常熟悉从来浏览器中分离出组件并无头地运行它们:Node.js 是 Google Chrome 的以无头方式运行的脚本引擎 (V8)。您应该能够自在地运行一个无头的渲染包来进行测试。

让我们回过头来看看 karma.conf.js,可以在 browsers 数组中看到 PhantomJS。现在您已经理解所有 Jasmine 客户端测试可以如何在浏览器中运行并通过,而不会看到启动 GUI。

配置 Karma 来启动更多浏览器

Karma 为所有主要浏览器都提供了启动器。如果回过头来查看 package.json 的 devDependencies 块,就可以看到已为 Firefox 和 Chrome 安装了启动器。如果您在计算机中安装了这些浏览器,可将它们添加到 karma.conf.js 中的 browsers 数组中,键入 grunt test 来在新添加的浏览器中运行您的测试套件。

您可以访问 npm 网站 并搜索 karma launcher 来查看所有支持的浏览器列表。可以键入 npm install karma-xxx-launcher --save-dev 来安装每个启动器并将它添加到 package.json 中。启动器安装后,将它添加到 karma.conf.js 中的 browsers 数组中并重新运行您的测试。

捕获无法启动的浏览器

Karma 启动器通常用于启动位于同一个计算机上的浏览器。回想一下,Karma 还可以用于在远程浏览器上运行测试 — 想想智能电话、平板电脑和智能电视。任何支持 Web 套接字的浏览器都可由 Karma 捕获并用作测试目标。

要捕获一个远程浏览器,首先必须让 Karma 服务器在执行不同测试的间隙正常运行。要保持 Karma 服务器永久地正常运行,可在 karma.conf.js 中将 singleRun 值更改为 false

// Continuous Integration mode
// If true, it capture browsers, run tests and exit
singleRun: true

如果重新启动 Karma 服务器或任何捕获的浏览器,它们会尝试重新连接和重新运行所有测试。

现在 Karma 服务器已在正常运行,可以在远程浏览器中通过 URL http://您服务器的 IP 地址:9876 访问它。这就是使用 Karma 捕获无法启动的浏览器的全过程。

添加其他 Karma 报告程序

熟悉如何添加其他测试和浏览器后,可以考虑添加其他报告程序来捕获和显示测试结果。

首先,将 dots 报告程序添加到 karma.conf.js 中的 reporters 数组中。下一次您键入 grunt test 时,您会看到屏幕上打印了一系列点 — 每个通过的测试一个点。

这些点就像昙花一现。除了在测试运行时观察屏幕,您如何知道有多少测试通过了?或许可以安装一个更持久的报告程序。

karma-html-reporter 最可能符合您的要求。如图 1 中的示例所示,您会获得每个测试的详细结果,这些结果具有清晰的 HTML 格式。

图 1. karma-html-reporter 生成的报告
karma-html-reporter 报告的屏幕截图
karma-html-reporter 报告的屏幕截图

要安装 karma-html-reporter,可以键入 npm install karma-html-reporter --save-dev。然后要配置它,可以像这样编辑 karma.conf.js:

reporters: ['progress', 'html'],

htmlReporter: {
  outputDir: 'karma_html'
},

参阅 karma-html-reporter包的详细信息 来查看完整的配置选项集合。

如果更喜欢原始 XML 输出,而不是漂亮的 HTML 输出,可以考虑安装 karma-junit-reporter。要安装它,可以键入 npm install karma-junit-reporter --save-dev。然后在 karma.conf.js 中配置它,如 项目网站 上所示。

您已在 npm 网站 上键入了 karma launcher 来搜索其他启动器。您应同样能键入 karma reporter 来查找其他 Karma 报告程序。

使用 Karma 和 istanbul 显示代码覆盖范围

只有在显示了测试代码覆盖范围后,测试基础架构才算完整。前面的报告仅显示了通过和失败的测试 — 它们没有显示您忘记编写的测试。优秀的代码覆盖工具会逐行地显示单元测试访问了您代码库的哪些部分,更重要的是,指出单元测试还未访问哪些代码行。

如果键入 npm install karma-coverage --save-dev 来安装 karma-coverage 插件(它使用了 istanbul 库),并根据 说明 来配置它,您会获得一组漂亮的报告,它们显示了应用程序中的每行代码,如图 2 所示。

图 2. 覆盖范围报告
一个 karma-coverage 报告的屏幕截图
一个 karma-coverage 报告的屏幕截图

绿色的行已由单元测试访问,红色的行是等待未来的单元测试的行。

模拟依赖关系

精心编写的单元测试的一个特征是它们的依赖关系。它们绝不应依赖于实际的数据库或向实际的 Web 服务发出实际的 HTTP 调用。幸运的是,模拟这些依赖关系是一种历史悠久的测试运行方式。

无需在客户端 Jasmine 测试中执行实际的 Ajax 调用,可以考虑使用 AngularJS 中包含的 $httpBackend 模拟服务。

无需依赖于实际的 MongoDB 数据库来执行测试,可以考虑使用 Mockgoose— Mongoose(和 MongoDB)的一个单纯的内存型临时替代方案,专为测试用途而编写。

使用 Protractor.js 运行端到端测试

截至目前,您一直在运行单元测试。单元测试 (根据定义)是一些不依赖于 GUI 的测试。单元测试用于您代码库的非 UI 部分。

但是,如何测试典型用户在使用您的应用程序时将执行的所有键入和按钮单击操作?要测试这种行为类型,您可以安装 Protractor.js。

Protractor 主页 包含丰富的说明和示例。这是简短版本:键入 npm install protractor --save-dev 来安装该库。接下来,编写 Jasmine 测试来访问特定的 URL 和与该页上的特定组件交互。清单 4 显示了来自该项目的主页的一个 Protractor 测试示例。

清单 4. 一个 Protractor 测试
describe('angularjs homepage todo list', function() {
  it('should add a todo', function() {
    browser.get('http://www.angularjs.org');

    element(by.model('todoText')).sendKeys('write a protractor test');
    element(by.css('[value="add"]')).click();

    var todoList = element.all(by.repeater('todo in todos'));
    expect(todoList.count()).toEqual(3);
    expect(todoList.get(2).getText()).toEqual('write a protractor test');
  });
});

您可能已经猜测到,此测试访问 AngularJS 主页,查找 todoText 元素,键入一个测试字符串,然后单击添加按钮。然后它运行一系列断言来确保出现预期的值。

结束语

正如我之前说过的(而且我常常说):一次测试胜过一千条意见。但只有在您拥有可靠的软件实践作为支撑时,无情的反驳才有用。如果您将从本文中学到的经验教训付诸于实现,您将会逐步实现成为这个快节奏、不断变化的软件生态系统的一员所需的 “工程严谨性”。


相关主题

  • AngularJS 单元测试:查阅 AngularJS 开发人员指南的单元测试部分。
  • MEAN.JS 测试文档:查看 MEAN.JS 文档中的测试部分。
  • Karma:在项目网站上查找 Karma 的所有信息和可用于 Karma 的 插件
  • PhantomJS:进一步了解这个可编写脚本、无头的浏览器。
  • Mocha:访问 Mocha 主页上的文档和示例。
  • Jasmine:查阅 Jasmine 文档。
  • developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
  • developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
  • 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Open source
ArticleID=1013597
ArticleTitle=精通 MEAN: 测试 MEAN 堆栈
publish-date=08202015