Nodejs测试:从0到90(理论篇)-阿里云开发者社区

开发者社区> 开发与运维> 正文

Nodejs测试:从0到90(理论篇)

简介: 最近在负责一个基于nodejs的应用,在很多方面都经历了一个从无到有的过程,测试也是如此。刚开始时,代码都写不好,更别提测试了,那时测试为0。经历过一段时间后, 测试覆盖率在90%+。这就是我的从0到90。剩下的10,还有很长的路,且待下回分解。

最近在负责一个基于nodejs的应用,在很多方面都经历了一个从无到有的过程,测试也是如此。刚开始时,代码都写不好,更别提测试了,那时测试为0。经历过一段时间后,尤其是看到 npm 上优秀的库的测试覆盖率都在100%时,痛下决心开始学习 nodejs 的测试, 目前这个应用的测试覆盖率在90%+。这就是我的从0到90。剩下的10,还有很长的路,且待下回分解。

写在前面的

对于开发者,测试的重要性毋庸置疑, 所谓”无测试不上线“,”无测试不重构“等。但在实践的过程中,测试总是有这样的那样的问题,随便列举下:

  • 不写测试。一般是觉得写测试浪费时间,也有可能是不会写;
  • 乱写测试。随意的写,没有规范,没有覆盖率,没有集成;
  • 写了跟没写一样。这样的测试通常是不可读、不可维护、不可信任,不可重复或独立运行(强依赖某些特定的环境或条件)、不执行(通常只是开发的时候执行一下,之后再也不会执行,或者执行太慢,没有人愿意执行)。

一个好的测试

一千个人眼中有至少二千种测试理念或方法,很难定义什么是一种好的测试。在团队一直负责测试的推进,我们的理念很简单,包括以下几个原则:

  • 覆盖75%+

一个好的测试,最重要的衡量标准就是测了多少(覆盖率),75%是最低的标准。这个标准对java来说基本可行,但对nodejs不太适用,javascript是弱类型的、动态语言,没有编译阶段,很多错误只能在运行时才会被发现,所以需要更高的覆盖率,最好是100%,目前个人的标准是90%+。

  • 可重复执行

每一个测试用例,无论在任何环境下,都应该可以反复执行,并产生相同的结果。只有这样,才能够相任你的测试,进而发现真正的bug。这也是集成测试最低要求。

  • 保持独立

一个测试用例只测试代码的某一方面,如一个分支,且不强依赖某些特定的环境或条件。

  • 可读、可维护、可信任
  • 快快快

无论是单个测试用例,还是集成测试,必须要保证测试执行足够的快。

测什么

测试测什么主要依据具体的需求、业务、成本、语言等,但也有一定的共性,单元测试准则 给出了一些准则可供参考,这里不再讨论。

怎么测

又是一个非常大的话题,本文仅从个人角度给出nodejs测试的工具及方法。

概述

框架

Nodejs 测试框架众多,目前使用最广的是Mocha。本文详细说明下 Mocha, 并简要介绍几种其它框架。本文的示例,如无特别说明,都是基于Mocha.

Mocha

mocha_logo

A simple, flexible, fun JavaScript test framework for node.js and the browser.

Mocha 是一个功能丰富的Javascript测试框架,它能运行在Node.js和浏览器中,支持BDD、TDD式的测试。

快速开始

  • 安装
npm install -g mocha
  • 写一个简的测试用例
var assert = require('chai').assert;
describe('Array', function() {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    });
  });
});
  • 运行
$ mocha

输出结果如下:

  .

   1 test complete (1ms)

使用

断言

Mocha 允许你使用任何你想用的断言库,包括:

  • should.js BDD style shown throughout these docs
  • expect.js expect() style assertions
  • chai expect(), assert() and should style assertions
  • better-assert c-style self-documenting assert()
  • unexpected the extensible BDD assertion toolkit
钩子 Hooks

Mocha 提供了 before(), after(), beforeEach(), afterEach()等 hooks,用于设置前置条件及清理测试,示例如下:

describe('hooks', function() {
  before(function() {
    // runs before all tests in this block
  })
  after(function(){
    // runs after all tests in this block
  })
  beforeEach(function(){
    // runs before each test in this block
  })
  afterEach(function(){
    // runs after each test in this block
  })
  // test cases
})
专用测试 或 跳过测试

专用测试允许只测试指定的测试集或用例,只需在测试集或用例前加.only(),示例如下:

describe('Array', function(){
  describe.only('#indexOf()', function(){
    ...
  })
})

跳过类似于 junit 的 @Ignore, 用于跳过或忽略指定的测试集或用例,只需在测试集或用例前加.skip(),示例如下:

describe('Array', function(){
  describe.skip('#indexOf()', function(){
    ...
  })
})
编辑器插件

除了使用 Mocha 提供的命令行外,还可以使用 编辑器插件 运行,目前支持了:

  • TextMate
  • JetBrains
  • Wallaby.js
  • Emacs

以JetBrains为例,JetBrain 为其 IDE 套件(IntelliJ IDEA, WebStorm等)提供了 NodeJS, 可以直接运行或调试 mocha 测试用例。基本步骤:

  • 安装 NodeJS 插件(如果没有安装的话,默认IntelliJ IDEA, WebStorm都已安装):通过 Preferences > Plugins 查找名为 NodeJS 的插件并安装;
  • 添加 mocha 测试。通过 Edit Configuration 添加 Mocha, 示例如下:

add_mocha_test

  • 运行或调试。支持按目录、文件、测试集、测试用例等运行或调试,运行示例如下:

run_mocha_test

其它

以下列举几个基于 nodejs 的测试框架或工具,它们可用于 nodejs 或 浏览器端javascript 代码的测试,不在本文讨论范围,详见官方文档。

Jasmine

jasmine_logo

A Behavior Driven Development JavaScript testing framework

Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.

更多见 官方文档.

Karma Runner

karma_logo

To bring a productive testing environment to developers

The main goal for Karma is to bring a productive testing environment to developers. The environment being one where they don't have to set up loads of configurations, but rather a place where developers can just write the code and get instant feedback from their tests.

更多见 官方文档.

工具

mocha 提供了测试的基本框架,在特定的场景下的测试还需要其它工具做以辅助,这个列举几个常用的工具。

SuperTest

SuperTest 提供对 HTTP 测试的高度抽象, 极大地简化了基于 HTTP 的测试。

The motivation with this module is to provide a high-level abstraction for testing HTTP, while still allowing you to drop down to the lower-level API provided by super-agent.

安装
$  npm install supertest --save-dev
使用示例
  • 简单的 HTTP 请求
var request = require('supertest');

describe('GET /user', function() {
  it('respond with json', function(done) {
    request(app)
      .get('/user')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200, done);
  });
});
  • 上传文件
request(app)
    .post('/')
    .field('name', 'my awesome avatar')
    .attach('avatar', 'test/fixtures/homeboy.jpg')
    // ..
  • 修改响应头或体
describe('GET /user', function() {
  it('user.name should be an case-insensitive match for "tobi"', function(done) {
    request(app)
      .get('/user')
      .set('Accept', 'application/json')
      .expect(function(res) {
        res.body.id = 'some fixed id';
        res.body.name = res.body.name.toUpperCase();
      })
      .expect(200, {
        id: 'some fixed id',
        name: 'TOBI'
      }, done);
  });
});

更多见 文档

Istanbul

代码覆盖(Code coverage)是软件测试中的一种度量,描述程式中源代码被测试的比例和程度,所得比例称为代码覆盖率。它有四个测量维度:

  • 行覆盖率(line coverage):是否每一行都执行了
  • 函数覆盖率(function coverage):是否每个函数都调用了
  • 分支覆盖率(branch coverage):是否每个if代码块都执行了
  • 语句覆盖率(statement coverage):是否每个语句都执行了

Istanbul 是 JavaScript 语言最流行的代码覆盖率工具。

快速开始
  • 安装
$ npm install -g istanbul
  • 运行

最简单的方式:

$ cd /path/to/your/source/root
$ istanbul cover test.js

输出运行结果:

  ..
  test/app/util/result.test.js
     should static create
     should be success
     should be static success
     should be error
     should be static error

  299 passing (13s)

[mochawesome] Report saved to /opt/source/node_modules/.mochawesome-reports/index.html

=============================== Coverage summary ===============================
Statements   : 92.9% ( 1505/1620 )
Branches     : 85.42% ( 410/480 )
Functions    : 94.33% ( 133/141 )
Lines        : 93.01% ( 1504/1617 )
================================================================================
Done

这条命令同时还生成了一个 coverage 子目录,其中的 coverage.json 文件包含覆盖率的原始数据,coverage/lcov-report 是可以在浏览器打开的覆盖率报告,如下图所示:

icov

模式

常见的测试模式包括TDD, BDD。

TDD (Test Driven Development)

测试驱动开发是敏捷开发中的一项核心实践和技术,也是一种设计方法论。TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。TDD的基本思路就是通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把需求分析,设计,质量控制量化的过程。整体流程如下:

tdd_flowchart

以一个阶乘的小程序为例。先写出的测试用例,如下所示。此时运行肯定会报错,因为被测试的程序还没有写。

var assert = require('assert'),
    factorial = require('../index');

suite('Test', function (){
    suite('#factorial()', function (){
        test('equals 1 for sets of zero length', function (){
            assert.equal(1, factorial(0));
        });

        test('equals 1 for sets of length one', function (){
            assert.equal(1, factorial(1));
        });

        test('equals 2 for sets of length two', function (){
            assert.equal(2, factorial(2));
        });

        test('equals 6 for sets of length three', function (){
            assert.equal(6, factorial(3));
        });
    });
});

开始写阶乘的逻辑,如下所示。

module.exports = function (n) {
    if (n < 0) return NaN;
    if (n === 0) return 1;

    return n * factorial(n - 1);
};

此时再运行之前的测试用例,看其是否通过,如果全部通过则表示开发完成,如不通过则反复修改被测试的逻辑,直到全部的通过为止。

BDD (Behavior Driven Development)

行为驱动开发是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。主要是从用户的需求出发,强调系统行为。其最显著的特征是,通过编写行为和规格说明来驱动软件开发。行为和规格说明看上去与测试十分相似,但它们之前还是有显著的不同。

还是以上面的阶乘为例,BDD模式的测试用例如下:

var assert = require('assert'),
    factorial = require('../index');

describe('Test', function (){
    before(function(){
        // Stuff to do before the tests, like imports, what not
    });

    describe('#factorial()', function (){
        it('should return 1 when given 0', function (){
            factorial(0).should.equal(1);
        });

        it('should return 1 when given 1', function (){
            factorial(1).should.equal(1);
        });

        it('should return 2 when given 2', function (){
            factorial(2).should.equal(2);
        });

        it('should return 6 when given 3', function (){
            factorial(3).should.equal(6);
        });
    });

    after(function () {
        // Anything after the tests have finished
    });
});

从示例上看,BDD的测试用例与TDD最大的不同在于其措辞,BDD读起来更像是一个句子。因此BDD的测试用例可作为开发者、QA和非技术人员或商业参与者之间的协同工具,对开发者来说,如果你可以非常流畅地读测试用例,自然能够写出更好、更全面的测试。

TDD vs BDD

TDD和BDD本质和目标都是一致的。只是在实施方法上,进行了不同的探讨来完善整个敏捷开发的体系。TDD的迭代反复验证是敏捷开发的保障,但没有明确如何根据设计产生测试,并保障测试用例的质量,而BDD倡导大家都用简洁的自然语言描述系统行为的理念,恰好弥补了测试用例(即系统行为)的准确性。

几乎所有基于Nodejs的库或应用都选择了BDD, 至于为什么,我还没有搞明白。

断言

目前比较流行的断言库包括:

  • should.js BDD style shown throughout these docs
  • expect.js expect() style assertions
  • chai expect(), assert() and should style assertions
  • better-assert c-style self-documenting assert()
  • unexpected the extensible BDD assertion toolkit

这些断言库除了风格上略有不同外,实现在功能几乎完全一致,可根据个人喜好或应用需求选择。

下一篇:Nodejs测试:从0到90(实践篇)

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章