前端开发中的测试及相关技术栈

Category:
发表:
大纲
  1. 1. 前言
  2. 2. 概念
    1. 2.1. 测试框架集
    2. 2.2. 测试套件
    3. 2.3. 测试用例
    4. 2.4. 断言
    5. 2.5. 测试范式
  3. 3. 测试框架集Mocha
    1. 3.1. 介绍及安装
    2. 3.2. 常规用法
    3. 3.3. 异步测试
    4. 3.4. 用在浏览器上
    5. 3.5. 生成测试用例报告
  4. 4. 参考列表

前言

测试是一个很大的topic。记得从大学时代的软件工程就开始接触测试这个概念,当时被灌输这样一种思想,软件工程领域把测试视为软件生命周期的一部分

其实当时我对这些东西是不明白的,因为软件工程本来就是一个非常学院派的研究课题,而我从某种程度上来说是一个实践派的角色。虽然现在的我仍然处于一个懵懂的状态,不过好在大体已经知道是怎么回事了。或者说,针对个别领域(前端开发中)我大概知道了测试的方方面面。

本文将介绍一些测试的相关概念,以及在前端开发中的地位以及如何去做。最后将会介绍一款前端测试的利器Mocha

概念

什么叫测试?它用用来干啥的?简单来说,软件工程中的测试就是用来保证软件质量的一种手段。软件测试这块是一个大的话题,我不会去详细阐述。这里我仅从实践的角度说一说前端开发(或者Node.js环境)中的相关内容。

在前端开发中,针对不同的目的,测试会有两种大的方向。一种是保证模块功能的单元测试,一种是保证UI(或者交互)的界面测试。

目前就整个社区来说,前者(单元测试)的解决方案已经比较成熟,不管是Node.js环境还是浏览器环境都会有相应的对应措施。

而后者(界面测试)仍然处在一个摸着石头过河的阶段。不过在Node.js环境的支持下,可以通过无头浏览器(headless browser),比如phantomjs,去做一些模拟用户操作页面的动作。虽然有这样一个解决方案,但是UI或者交互自动化测试确确实实是一个大坑,目前的做法在我看来其实还是靠人力堆上去的,还没有达到自动化的高度。

本篇文章将会从单元测试(功能测试)的角度来阐述前端开发中的遇到的测试需求,以及介绍一些常用工具集。

基本上在测试中会用到如下的几个概念:测试框架集、测试套件、测试用例、断言、测试范式。

测试框架集

所谓测试框架集,就是运行测试方方面面内容的一个容器。目前社区里常用的几个测试框架集有MochaJasmineKarmaTape等。

因为后面我们要以mocha作为讲解对象,所以后面的示例都是mocha环境。

测试套件

所谓测试套件(test suite)指的是,一组针对软件规格的某个方面的测试用例。也可以看作,对软件的某个方面的描述(describe)。看下面的一个示例,

describe('加法函数的测试', function() {
    // TODO
});

上面示例中就是使用describe语法声明了一个测试套件。加法函数的测试是这个测试套件的名字,回调函数的内容就是此测试套件具体要做的事情。

此外,测试套件是可以嵌套的,比如,

describe('四则运算的测试', function() {
    describe('加法测试', function() {
        // TODO
    });

    describe('减法测试', function() {
        // TODO
    });

    // ...
});

测试用例

所谓测试用例,就是在测试套件中定义的具体测试内容。一般来说测试要尽可能的覆盖各个边界。比如,

describe('加法函数的测试', function() {
    it('1 + 1 = 2', function() {
        expect(1 + 1).to.be.equal(2);
    });

    it('0 + 0 = 0', function() {
        expect(0 + 0).to.be.equal(0);
    });

    // more test case.
});

如上所示,describe中的it语法就是用来声明测试用例的。它的本质其实还是个函数,第一个参数是测试用例的名称,第二个参数是一个回调函数,里面定义了具体的断言逻辑。

那么,示例中的expect这样的语法又是啥意思呢?它就是我们接下来要说的测试断言了。

断言

何为断言?所谓断言,简单的来说就是对代码行为的一个预测。而代码的最终行为要么是符合你的预测,要么就是不符合你的预测。前者意味着断言通过,后者即断言失败,而断言失败将会导致测试用例不通过。

Node.js中内置了一个assert模块,这里的是它的文档(中文版文档在这里)。

现在你应该大概知道断言是怎么一回事了。那么问题又来了,前面的示例中,

expect(1 + 1).to.be.equal(2);

这样一行代码也叫断言么?怎么跟Node.js提供的内置assert模块的语法不一样呢?

这个问题涉及到一个断言风格的问题。

主流的断言风格有如下几种,每一种断言风格的语法糖都不太一样,

断言风格 特征 说明 示例 开源库
assert assert TDD经典的assert方式 assert.equal(variable, "value") 内置支持, chai.js, better-assert
expect expect 在BDD中最为常见,提供链式语法 expect(variable).to.equal("value") expect.js, chai.js
should should 也是一种常用的BDD断言方式 variable.should.equal("value") should.js, chai.js, unexpected

从上表中,可以看出chai.js基本上都支持这三种主流的断言风格,所以一般社区里面大家起的新项目都会逐渐转向chai.js。

其实博主最早是使用should.js的,不过还是抵不住expect语法所拥有的特性,良好的链式结构以及仿自然语言的语境,无疑更加有吸引力。

关于各个断言库的具体用法这里就不作细述了,上面的表格中都给出了链接,可自行查阅。(智力正常的人,应该都能快速上手。因为真的很简单。)

测试范式

最后再聊聊测试范式。

不知道你有没有注意,在前面提及断言那块内容时,出现了两个缩写简称:BDD和TDD。他们是什么意思呢?

专业点来说,BDD和TDD是软件工程中针对软件测试提出的两种方法论。

简单来说,就是两种不同的测试目的。

BDD是“行为驱动的开发”(Behavior-Driven Development)的简称,指的是写出优秀测试的最佳实践的总称。BDD认为,不应该针对代码的实现细节写测试,而是要针对行为写测试。BDD测试的是行为,即软件应该怎样运行

BDD接口一般提供以下方法,

  • describe(),声明测试套件
  • it(),声明测试用例
  • before(),所有测试用例的前置动作
  • after(),所有测试用例的后置动作
  • beforeEach(),每个测试用例的前置动作
  • afterEach(),每个测试用例的后置动作

BDD在前面已经有不少示例代码了,这里就不给出示例代码了。

TDD是测试驱动开发(Test-Driven Development)的简称,它的组织方式是使用测试集(suite)和测试(test)。

每个测试集都有setupteardown函数。这些方法会在测试集中的测试执行前执行,它们的作用是为了避免代码重复以及最大限度使得测试之间相互独立。

TDD接口提供的方法如下,

  • suite:类似BDD中describe()
  • test:类似BDD中it()
  • setup:类似BDD中before()
  • teardown:类似BDD中after()
  • suiteSetup:类似BDD中beforeEach()
  • suiteTeardown:类似BDD中afterEach()

下面给出一段示例代码,

var assert = require("assert");

suite('Array', function() {
    setup(function() {
        console.log('测试执行前执行');
    });

    suite('#indexOf()', function() {
        test('当值不存在时应该返回 -1', function() {
            assert.equal(-1, [1,2,3].indexOf(4));
        });
    });
});

测试框架集Mocha

前面我们基本上将前端开发中能够涉及到的测试相关内容都给出了相关的描述。在最开始的时候,我们提到了一个测试框架集Mocha,下面我们就来说一说如何使用mocha来构建整个前端开发过程中的测试问题。

介绍及安装

Mocha是我的偶像之一TJ Holowaychuk发起的一个项目。它拥有很多特性,而且非常极客,使用方法也很简单。总之你知道它很吊就是了。😄😄

Mocha既可以用在Node.js环境也可以用在浏览器环境。我们先来看看在Node.js环境中是如何使用Mocha的。

首先,我们通过npm来安装mocha。

$ npm install -g mocha

一般来说,在具体的项目中,我们还需要将mocha安装到devDependencies中,因为mocha的工作是用来做测试的,所以它不应该出现在dependencies依赖中。

还有一点需要说明的是,mocha并不限制你使用何种断言风格,言下之意,市面上的几种流行断言库,它都是支持的。

常规用法

接下来我们介绍一下在项目中如何去实际的使用mocha。

假如我们的项目文件结构如下,

|- controllers
    - division.js
|- models
|- views
|- services
- app.js
- package.json

我们在根目录上新建一个文件夹,叫做test。我们所有的测试文件都将放在这个文件夹中。

假设我们的division.js内容如下,

function division(x, y) {
    return x / y;
}

module.exports = division;

我们的需求是要测试这个做除法运算的文件。测试内容应该包括各种边界情况。我们首先新建一个division.test.js文件,其内容如下,

var division = require('../controllers/division');
var expect = require('chai').expect;

describe('test division.js', function() {
    it('10/2 = 5', function() {
        expect(division(10, 2)).to.be.equal(5);
    });

    it('0/2 = 5', function() {
        expect(division(0, 2)).to.be.equal(0);
    });

    it('2/0 = Infinity', function() {
        expect(division(2, 0)).to.be.equal(Infinity);
    });
});

注意,这里我们使用chai断言库,而且使用的是expect断言风格。

测试文件中定义了一个测试套件,名字叫做”test division.js”,这个测试套件下又定义了3个测试用例。现在我们来运行mocha,看看测试的结果如何。

 test division.js
    ✓ 10/2 = 5
    ✓ 0/2 = 5
    ✓ 2/0 = Infinity


  3 passing (9ms)

从结果来看,我们写的三个测试用例都通过了。非常棒!

如果假设我们现在是个小学生,我们会以为’2/0=0’。修改相关的测试用例,再看看mocha执行的结果是什么。

异步测试

在对某些业务逻辑进行测试的时候,很可能会遇到这样一种情况。业务中可能包含异步逻辑。这时候我们的测试用例该如何来写呢?

我们看一个例子,

var request = require('superagent');
var expect = require('chai').expect;

describe('async test', function () {
    it('异步请求应该返回一个对象', function (done) {
        request
            .get('https://api.github.com')
            .end(function(err, res){
                expect(res).to.be.an('object');
                done();
            });
    });
});

其结果如下,

async test
    ✓ 异步请求应该返回一个对象             (1258ms)


  1 passing (1s)

这个例子中我们使用superagent来请求一个远程api,在请求返回的回调函数中,我们进行了断言判断。注意,这里在声明测试用例时,我们给回调函数传入了一个done参数,这个参数其实也是一个函数。我们在断言语句之后,显式的调用这个函数,表明异步任务语句结束了。从结果中也能看出,这个测试用例总共耗时1258ms。

如果某个异步操作的耗时非常长,超过了2000ms,那么此时mocha会默认这个测试为失败。比如,

// timeout.test.js
var expect = require('chai').expect;

describe('timeout test', function() {
    it('should finished in 5000ms', function(done) {
        var x = true;
        var f = function() {
            x = false;
            expect(x).to.be.not.ok;
            done();
        };
        setTimeout(f, 4000);
    });
});

它的结果是如下,

timeout test
    1) should finished in 5000ms


  0 passing (2s)
  1 failing

  1) timeout test should finished in 5000ms:
     Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

可见,我们的测试用例并没有通过。mocha认为如果一个测试用例2000ms还没有结束,那么就默认这个测试用例不通过。这种情况下,我们可以通过下面的方式来修改这个默认超时时间。

$ mocha timeout.test.js -t 5000

用在浏览器上

前面都是在说Node.js环境下如何去使用mocha,其实我们也可以在browser环境使用mocha。

在命令行中执行mocha init,这个命令会在目录下生成一个index.html文件,然后我们将mocha.jschai.js以及测试文件和待测试文件加入到这个html中。比如,

<!DOCTYPE html>
<html>
  <body>
    <h1>Unit.js tests in the browser with Mocha</h1>
    <div id="mocha"></div>
    <script src="mocha.js"></script>
    <script src="chai.js"></script>
    <script src="division.js"></script>
    <script>
      mocha.setup('bdd');
    </script>
    <script src="division.test.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

然后我们直接使用浏览器打开就可以了。效果如下。

生成测试用例报告

mocha内置了多种测试报告风格,默认使用的是spec风格。我们可以通过,

$ mocha --reporter [report-style]

来选择不同的报告风格。

$ mocha --reporters将会列出所有内置的风格。可以根据自己的喜好选择使用。

除了内置的报告风格之外,我们还可以使用一个叫做mochawesome的第三方模块,来生成比较美观的html文档报告。

其用法如下,

$ npm install --save-dev mochawesome
$ mocha --reporter mochawesome

它将会生成一个mochawesome-reports的文件夹,打开里面的index.html就可以看到测试报告了。效果如下,

参考列表