原则:谁开发,谁测试。
注意: 原则上应该先写测试,再进行编码;如果需求时间紧,可以先进行功能实现,但务必后续维护时候将测试代码补充完善。
BDD(优先)+TDD(完全代码覆盖)
常见的组合:
Test Driven Development,(单元)测试驱动开发。
特点:
示例:
describe('Lib Common', function () {
'use strict';
it('isEmpty', function () {
// isObject
isEmpty({}).should.be.equal(true);
isEmpty([]).should.be.equal(true);
isEmpty({ a: 1 }).should.be.equal(false);
isEmpty([1, 2]).should.be.equal(false);
// isString
isEmpty('').should.be.equal(true);
isEmpty('sth').should.be.equal(false);
// isNumber
isEmpty(0).should.be.equal(true);
isEmpty(0.1).should.be.equal(false);
// null and undefined
isEmpty(null).should.be.equal(true);
isEmpty(undefined).should.be.equal(true);
// boolean
isEmpty(false).should.be.equal(true);
isEmpty(true).should.be.equal(false);
// 最后一行false
isEmpty(isEmpty).should.be.equal(false);
});
it('md5/sha1', function () {
md5('sth').should.equal('7c8db9682ee40fd2f3e5d9e71034b717');
sha1('sth').should.equal('dec981e3bbb165d021029c42291faf06f59827c1');
});
it('authcode', function () {
authcode(authcode('test'), 'DECODE').should.be.equal('test');
authcode(authcode('test', 'ENCODE', 'key'), 'DECODE', 'key').should.be.equal('test');
authcode('c008AsZqmGL8VuEVpZKVlbPwXzSsCZ+YX5K5CAGpMMqn', 'DECODE').should.be.equal('');
});
});
Behavior Driven Development,行为驱动开发。
特点:
示例:
/* 测试路由 */
app.get('/test/model/mysql/init/ok', function (req, res) {
'use strict';
return db
.opensips('v1/subscriber')
.then(function () {
res.send(200, 'ok');
})
.catch(function (err) {
logger('routes/test/model/mysql/ok', err);
res.send(403, 'fail');
});
});
app.get('/test/model/mysql/init/fail', function (req, res) {
'use strict';
return db
.opensips('test/notExisted')
.then(function () {
res.send(200, 'OK');
})
.catch(function () {
res.send(200, 'fail');
});
});
/* 测试脚本 */
describe('Demo', function () {
'use strict';
it('404 not found', function (next) {
request(app)
.get('/sth/not/exist')
.set('Accept', 'text/plain')
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
should(res.body.status).be.equal(0);
next();
});
});
it('403 not allowed', function (next) {
request(app)
.get('/v2/basic/mqtt')
.set('Accept', 'text/plain')
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
should(res.body.status).be.equal(0);
next();
});
});
it('Init opensips/subscriber Should be OK', function (next) {
request(app)
.get('/test/model/mysql/init/ok')
.set('Accept', 'text/plain')
.expect(200)
.expect('ok')
.end(function (err) {
if (err) {
//console.log(res.body);
throw err;
}
next();
});
});
it('Init test/subscriber Should be FAILED', function (next) {
request(app)
.get('/test/model/mysql/init/fail')
.set('Accept', 'text/plain')
.expect(200)
.expect('fail')
.end(function (err) {
if (err) {
//console.log(res.body);
throw err;
}
next();
});
});
});
ES6 下的 BDD 测试示例对比:
import { test, server, assert } from './_import';
let location;
test.before(async () => {
const response = await server.inject({
method: 'POST',
url: '/login',
payload: {
username: 'willin',
password: 'PASSWORD'
}
});
location = response.headers.location;
});
test('GET / 302', async () => {
const response = await server.inject({
method: 'GET',
url: '/'
});
assert.equal(response.statusCode, 302);
});
test('GET /login 200', async () => {
const response = await server.inject({
method: 'GET',
url: '/login'
});
assert.equal(response.statusCode, 200);
});
test('POST /login 302', async () => {
const response = await server.inject({
method: 'POST',
url: '/login',
payload: {
username: 'willin',
password: 'PASSWORD'
}
});
assert.equal(response.statusCode, 302);
});
test('POST /login 401', async () => {
const response = await server.inject({
method: 'POST',
url: '/login',
payload: {
username: 'willin',
password: 'Ww10842073305zZa28v3PO5Ok0L63IdA'
}
});
assert.equal(response.statusCode, 401);
});
test('POST /login Invalid Params 403', async () => {
const response = await server.inject({
method: 'POST',
url: '/login',
payload: {
username: 'willin'
}
});
assert.equal(response.statusCode, 403);
});
test('GET /doc 200', async () => {
const response = await server.inject({
method: 'GET',
url: location
});
assert.equal(response.statusCode, 200);
});
test('GET /doc 302', async () => {
const response = await server.inject({
method: 'GET',
url: '/doc?'
});
assert.equal(response.statusCode, 302);
});
性能对比测试框架 Matcha: https://github.com/logicalparadox/matcha
技术选型,如图形验证码,在 NPM 包选取使用canvas
还是ccap
时可以用。
或,一个问题,有多种解决方案,选择采用哪一种方案的时候。
注意: 所有需要做选择的场景,最好都先做一下对比。
ATL (After v1.0.1)
if > (true) .................................... 4,752,967 op/s
if = (true) .................................... 4,653,896 op/s
if < (false) ................................... 4,612,560 op/s
Left Shift (ATL v1.0.0)
<< > (true) .................................... 2,562,098 op/s
<< = (true) .................................... 2,473,787 op/s
<< < (false) ................................... 2,458,286 op/s
suite('ATL', function () {
bench('if > (true)', function () {
atl('1.6.7', '1.4.4');
});
bench('if = (true)', function () {
atl('1.4.4', '1.4.4');
});
bench('if < (false)', function () {
atl('1.1.6', '1.4.4');
});
});
suite('Left Shift', function () {
bench('<< > (true)', function () {
atls('1.6.7', '1.4.4');
});
bench('<< = (true)', function () {
atls('1.4.4', '1.4.4');
});
bench('<< < (false)', function () {
atls('1.1.6', '1.4.4');
});
});
源码位于: https://github.com/WulianCC/node-atl/blob/master/benchmark/parse.js
按照上面推荐方式完成代码后,需要进行代码的测试。
首先需要明确业务的流程,理清测试的思路。
主要有两种设计思路:
这是传统的单元测试衍生而来的 BDD 测试方式。
这里测试用例的个数应该为8
次:
其中,测试 3-5 可以优化为一次测试(即根据所有管理员 uid 的数组比较是否包含当前用户 uid),最终优化后的结果应当为6
次。
但由于该思路中不明确用户,所以用户行为无法准确表达,在创建测试数据的时候较为困难,不仔细思考分析,无法优化需要创建多少条测试数据。
而实际上 BDD 测试为用户行为测试,可以以几类用户的情形分别进行测试。
以此循环,直至覆盖所有。
非常简洁明了的关系,需要 3 个测试用户,3 个组织(上下级关系进行数据复用,一个无权限的组织),即可涵盖所有范围。
最终优化版设计:
两个用户,三个组织。完成所有覆盖。
可以从上述测试思路二中进行反推。
实际上思路可能是在写代码或者写测试的过程中不断的改进和完善的。