1. 为什么要写测试?
Web 应用程序越来越复杂,这意味着有更多的可能出错。测试是帮助我们提高代码质量、降低错误的最好方法和工具之一。
- 测试可以确保得到预期结果。
- 加快开发速度。
- 方便维护。
- 提供用法的文档。
通过测试提供软件的质量,在开始的时候,可能会降低开发速度。但是从长期看,尤其是那种代码需要长期维护、不断开发的情况,测试会大大加快开发速度,减轻维护难度。
2. 测试的类型
2.1 单元测试
单元测试(unit testing)指的是以软件的单元(unit)为单位,对软件进行测试。单元可以是一个函数,也可以是一个模块或组件。它的基本特征就是,只要输入不变,必定返回同样的输出。
“单元测试”这个词,本身就暗示,软件应该以模块化结构存在。每个模块的运作,是独立于其他模块的。一个软件越容易写单元测试,往往会暗示着它的模块化结构越好,各模块之间的耦合性就越弱;越难写单元测试,或者每次单元测试,不得不模拟大量的外部条件,很可能暗示软件的模块化结构越差,模块之间存在较强的耦合。
单元测试的要求是,每个模块都必须有单元测试,而软件由模块组成。
单元测试通常采取断言(assertion)的形式,也就是测试某个功能的返回结果,是否与预期结果一致。如果与预期不一致,就表示测试失败。
单元测试是函数正常工作、不出错的最基本、最有效的方式之一。每一个单元测试发出一个特定的输入到所要测试的函数,看看函数是否返回预期的输出,或者采取了预期的行动。单元测试证明了所测试的代码行为符合预期。
单元测试有助于代码的模块化,因此有助于长期的重用。因为有了测试,你就知道代码是可靠的,可以按照预期运行。从这个角度说,测试可以节省开发时间。单元测试的另一个好处是,有了测试,就等于就有了代码功能的文档,有助于其他开发者了解代码的意图。
单元测试应该避免依赖性问题,比如不存取数据库、不访问网络等等,而是使用工具虚拟出运行环境。这种虚拟使得测试成本最小化,不用花大力气搭建各种测试环境。
一般来说,单元测试的步骤如下。
- 准备所有的测试条件
- 调用(触发)索要测试的函数
- 验证运行结果是否正确
- 还原被修改的记录
2.2 其他测试类型
(1)集成测试
集成测试(Integration test)指的是多个部分在一起测试,比如测试一个数据库连接模块,是否能够连接数据库。
(2)功能测试
功能测试(Functional test)指的是,自动测试整个应用程序的某个功能,比如使用Seleniu木工锯自动打开浏览器运行程序。
(3)端对端测试
端对端测试(End-to-End testing)指的是全链路测试,即从开始端到终止端的测试,比如测试从用户界面、通过网络、经过应用程序处理、到达数据库,是否能够返回正确结果。端对端测试的目的是,确保整个系统能够正常运行,各个子系统之间依赖关系正常,数据能够在子系统之间、模块之间正确传递。
(4)冒烟测试
冒烟测试(smoke testing)指的是,正式的全面测试开始之前,对主要功能进行的预测试。它的主要目的是,确认主要功能能否满足需要,软件是否能运行。冒烟测试可以是手工测试,也可以是自动化测试。
这个名字最早来自对电子元件的测试,第一次电子元件通电,看看它是否会冒烟,如果没有冒烟,说明通过了测试;如果电流达到某个临界点之后,才出现冒烟,这时可以评估是否能够接受这个临界点。
3. 开发模式
测试不仅能够验证软件功能、保证代码质量,也能够影响软件开发的模式。
3.1 TDD模式
TDD是“测试驱动的开发”(Test-Driven Development)的简称,指的是先写好测试,然后再根据测试完成开发。使用这种开发方式,会有很高的测试覆盖率。
TDD的开发步骤如下。
- 先写一个测试。
- 写出最小数量的代码,使其能够通过测试。
- 优化代码。
- 重复前面散步。
TDD开发的测试覆盖率通常在90%以上,这意味着维护代码和新增特性会非常容易。因为测试保证了你可要信任这些代码,修改它们不会破坏其他代码的运行。
TDD接口提供以下四个方法。
- suite()
- test()
- setup()
- teardown()
下面代码是测试计数器是否加1。
suite('Counter', function () {
test('tick increases count to 1', function () {
var counter = new Counter();
counter.tick();
assert.equal(counter.count, 1)
});
});
3.2 BDD
BDD是“行为驱动的开发”(Behavior-Driven Development)的简称,指的是写出优秀测试的最佳实践的总称。
BDD认为,不应该针对代码的实现细节写测试,而是针对行为写测试。BDD测试的是行为,即软件应该怎样运行。
BDD接口提供以下六个方法。
- describe()
- it()
- before()
- after()
- beforeEach()
- afterEach()
下面是测试计数器是否加1的BDD写法。
describe('Counter', function () {
it('should increase count by 1 after calling tick', function () {
var counter = new Counter();
var expectedCount = counter.count + 1;
counter.tick();
assert.equal(counter.count, expectedCount);
})
});
下面是一个BDD开发的示例。现在,需要开发一个==Foo==类,该类的实例有一个==sayHi==方法,会对类参数说“Hi”。这就是==Foo==类的规格,根据这个规格,我们可以写出测试用例文件==foo.spec.js==。
describe('Simple object', function () {
var foo;
beforeEach(function () {
foo = new Foo('John');
});
it('should say hi', function () {
expect(foo.sayHi()).toEqual('John says hi!');
});
});
有了测试用例以后,我们再写出实际的脚本文件==foo.js。==
function Foo(name) {
this.name = name;
}
Foo.prototype.sayHi = function () {
return this.name + 'says hi!';
}
为了把测试用例与脚本文件分开,我们通常把测试用例放在==test==子目录之中。然后,我们就可以使用Mocha、Jasmine等测试框架,执行测试用例,看看脚本文件是否通过测试。
3.3 BDD术语
(1)测试套件
测试套件(test suite)指的是,一组针对软件规格的某个方面的测试用例。也可以看作,对软件的某个方面的描述(describe)。
测试套件由一个==describe==函数构成,它接受两个参数:第一个参数是字符串,表示测试套件的名字或标题,表示将要测试什么;第二个参数是函数,用来实现这个测试套件。
describe ('A suite', function () {
// ...
});
(2)测试用例
测试用例(test case)指的是,针对软件一个功能点的测试,是软件测试的最基本单位。一组相关的测试用例,构成一个测试套件。测试用例由it函数构成,它与describe函数一样,接受两个参数:第一个参数是字符串,表示测试用例的标题,表示测试用例的标题;第二个参数是函数,用来实现这个测试用例。
describe ('A suite', function () {
it('contains spec with an expectation', function () {
// ...
});
});
(3)断言
断言(assert)指的是对代码行为的预期。一个测试用例内部,包含一个或多个断言(assert)。
断言会返回一个布尔值,表示代码行为是否符合预期。测试用例之中,只要有一个断言为false,这个测试用例就会失败,只要所有断言都未true,测试用例才会通过。
describe('A suite', function () {
it('contains spec with an expectation', function () {
expect(true).toBe(true);
});
});
4.断言
断言是判断实际值与预期值是否相等的工具。
断言有assert、expect、should三种风格,或者称为三种写法。
// assert风格
assert.equal(event.detail.item, '(item)');
// expect风格
expect(event.detail.item).to.equal('(item)');
// should风格
event.detail.item.should.equal('(item)');
Chai.js是一个很流行的断言库,同时支持上面三种风格。
(1)assert风格
var assert = require('chai').assert;
var foo = 'bar';
var beverages = {tea: ['chai', 'matcha', 'oolong']};
assert.typeOf(foo, 'string', 'foo is a string');
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
上面代码中,assert方法的最后一个参数是错误提示信息,只有测试没有通过时,才会显示。
(2)expect风格
var expect = require('chai').expect;
var foo = 'bar';
var beverages = {tea: ['chai', 'matcha', 'oolong']};
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(beverages).to.have.property('tea').with.length(3);
(3)should风格
var should = require('chai').should();
var foo = 'bar';
var beverages = {tea: ['chai', 'matcha', 'oolong']}
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
beverages.should.have.property('tea').with.length(3);
预期的终极目标
以上内容不属于原创,只是整理的一些笔记。在之前的工作中,总会听到后端开发有个词叫单元测试,隐约觉得那是一种可以后台写代码自动测试自己的程序是否正确,觉得有点高大上,有点可望不可即。后来写vue项目,在官方文档中也看到这个词,觉得可能在不久的将来,前端为自己的程序编写类似的这种测试用例也是一种必备技能,就算不是专门去写node程序,可能以后面试vue,别人会问你有没有了解过vue里面的单元测试?有没有为自己的项目编写测试用例?
如何继续深入
- [ ] - 了解mocha,跑一遍官网的例子。
- 参考资料:
- mocha官网,
- 大牛翻译的中文版
- [ ] - 了解下 Travis CI。
- 参考资料:
- travis-ci官网
- 阮一峰 Travis CI 教程
- 厚颜无耻的奉上我的两个小案例,案例一,案例二
- [ ] - 了解下karma
- 参考资料:
- karma官网
- 前端自动化测试工具--使用karma进行javascript单元测试
- [ ] - 了解下vue中的单元测试
- 参考资料:
- vue官方文档-单元测试章节
- Vue Test Utils