JavaScript 测试基础,TDD、BDD、Benchmark

2023-07-25 00:00:00

原则:谁开发,谁测试。

注意: 原则上应该先写测试,再进行编码;如果需求时间紧,可以先进行功能实现,但务必后续维护时候将测试代码补充完善。

BDD(优先)+TDD(完全代码覆盖)

测试框架

常见的组合:

  • ES5: mocha + istanbul
  • ES6: ava + nyc

TDD

Test Driven Development,(单元)测试驱动开发。

特点:

  1. 直接引用对应源码,执行方法进行测试;
  2. 测试用例须设计完整,把所有分支都 Cover 到。

示例:

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('');
  });
});

BDD

Behavior Driven Development,行为驱动开发。

特点:

  1. 运行系统,模拟用户请求进行访问;
  2. 行为分析要完整,要将可能所有结果覆盖。

示例:

/* 测试路由 */
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);
});

Benchmark

性能对比测试框架 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

当我写一段测试的时候,我在想些什么

按照上面推荐方式完成代码后,需要进行代码的测试。

首先需要明确业务的流程,理清测试的思路。

  • 成功
  • 错误
    • 错误 1:用户未加入组织
    • 错误 2:传入参数组织不存在
    • 错误 3:用户无组织权限

主要有两种设计思路:

设计思路

思路一
  1. 完成测试用例,覆盖成功的所有情况
  2. 完成测试用例,覆盖错误 1 的所有情况
  3. 完成测试用例,覆盖错误 2 的所有情况
  4. 完成测试用例,覆盖错误 3 的所有情况

这是传统的单元测试衍生而来的 BDD 测试方式。

这里测试用例的个数应该为8次:

  • 成功:
    • 1.当前组织的用户有传入组织 oid
    • 2.当前组织的用户未传入组织 oid
    • 3-5.上级组织,上上级组织,根级组织的管理员用户传入组织 oid
  • 6.失败 1:用户未加入组织
  • 7.失败 2:传入参数组织不存在
  • 8.失败 3:用户无组织权限

其中,测试 3-5 可以优化为一次测试(即根据所有管理员 uid 的数组比较是否包含当前用户 uid),最终优化后的结果应当为6次。

但由于该思路中不明确用户,所以用户行为无法准确表达,在创建测试数据的时候较为困难,不仔细思考分析,无法优化需要创建多少条测试数据。

思路二

而实际上 BDD 测试为用户行为测试,可以以几类用户的情形分别进行测试。

  1. 模拟一个用户的数据,覆盖成功和可能错误(有可能无法涵盖到所有错误)的所有情况
  2. 根据未覆盖的部分,再模拟另一个用户的数据,覆盖成功和可能错误(有可能无法涵盖到所有错误)的所有情况

以此循环,直至覆盖所有。

  • 用户 1(非组织管理员,查询自己的组织)
    • 1.成功(未传入组织 oid)(组织 1)
    • 2.成功(传入组织 oid)
    • 3.失败 2:传入参数组织不存在
    • 4.失败 3:用户无组织权限(组织 2)
  • 用户 2(上级某组织管理员)(组织 3)
    • 5.成功
  • 用户 3(未加入组织用户)
    • 6.失败 1:用户未加入组织

非常简洁明了的关系,需要 3 个测试用户,3 个组织(上下级关系进行数据复用,一个无权限的组织),即可涵盖所有范围。

最终优化版设计:

  • 用户 1(某组织管理员,有下级组织)
    • 1.成功(未传入组织 oid,查询自己的组织)
    • 2.成功(传入当前的组织 oid(组织 1))
    • 3.成功(传入下级的组织 oid(组织 2))
    • 4.失败 2:传入参数组织不存在
    • 5.失败 3:用户无组织权限
  • 用户 2(未加入组织用户)
    • 6.失败 1:用户未加入组织(组织 3)

两个用户,三个组织。完成所有覆盖。

当我以测试驱动开发的时候,我在想些什么

可以从上述测试思路二中进行反推。

实际上思路可能是在写代码或者写测试的过程中不断的改进和完善的。

  • 如果已经写好了测试正在写代码,可以及时回过头来调整测试;
  • 如果功能写好了又再重新测试,可以在测试优化后再去看逻辑代码是否还有优化的空间。
更多推荐

原生微信小程序中进行 API 请求

原生微信小程序中进行API请求当在原生微信小程序中进行API请求时,封装请求可以提高代码的可维护性和可扩展性。在本篇博客中,我们将一步步介绍如何进一步封装请求,并添加请求超时、拦截器和请求取消功能。第一步:基本请求封装首先,我们创建一个用于发送HTTP请求的基本封装。在微信小程序中,我们使用wx.request发送请求

day41 jdk8新特性Stream流 数据库安装

流(Stream)中保存了对集合或者数组数据的操作,和集合类似,但是集合中保存的是数据。Stream不能保存数据一、创建流通过Collection对象的stream()或者parallelStream()通过Arrays类的stream(Array[]<T>)方法通过Stream接口of()iterate()gener

Java版本spring cloud + spring boot企业电子招投标系统源代码

项目说明随着公司的快速发展,企业人员和经营规模不断壮大,公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境,最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范,以及审计监督要求;通过电子化平台提高招投标工作的公开性和透明性;通过电子化招投标,使得招标采购的质量更高、速度

计算机视觉与深度学习-经典网络解析-ZFNet-[北邮鲁鹏]

这里写目录标题ZFNet主要改进减小第一层卷积核将第二、第三个卷积层的卷积步长都设置为2增加了第三、第四个卷积层的卷积核个数ZFNetZFNet是一种基于AlexNet的模型,由MatthewD.Zeiler和RobFergus在2013年提出。相对于AlexNet,ZFNet结构与AlexNet网络结构基本一致,进行

阿里云服务器ECS_云主机_服务器托管_计算性能介绍

阿里云服务器是什么?云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务,云服务器可以降低IT成本提升运维效率,免去企业或个人前期采购IT硬件的成本,阿里云服务器让用户像使用水、电、天然气等公共资源一样便捷、高效地使用服务器。阿里云服务器具有安全、稳定、弹性升降配、高性能、易用可扩展等优势。阿里云百科来详细说下什么是阿

使用 rtty 进行远程 Linux 维护和调试

rtty是一个用于在终端上进行远程连接和数据传输的工具。它提供了一种简单的方式来与远程设备进行通信,使得在不同主机之间传输数据变得更加方便。安装rtty是一个可执行程序,可以在Linux、macOS和Windows等平台上使用。Linux/macOS在终端中执行以下命令,使用curl下载rtty可执行文件:curl-L

Golang代码漏洞扫描工具介绍——trivy

Golang代码漏洞扫描工具介绍——trivyGolang作为一款近年来最火热的服务端语言之一,深受广大程序员的喜爱,笔者最近也在用,特别是高并发的场景下,golang易用性的优势十分明显,但笔者这次想要介绍的并不是golang本身,而且golang代码的漏洞扫描工具,毕竟作为服务端的程序,安全性一直是一个不同忽视的地

新势力在智能化路上,正抢了Tier 1的生意

作者|&nbsp;Amy编辑|&nbsp;德新上半年的汽车行业价格内卷,下半年则一下资本涌入,风起云涌。先是蔚来拿到了11亿美元来自中东的投资,紧接着7月大众以7亿美元投资小鹏汽车,8月哪吒完成70亿元Crossover轮投资。传闻中,还有大众捷达与Stelliantis两大集团接洽零跑汽车,秘密洽谈投资收购以及潜在的

.NET超简单轻量级的HTTP请求组件Flurl

简介Flurl是一个用于构建基于HTTP请求的C#代码的库。它的主要目的是简化和优雅地处理网络请求(只用很少的代码完成请求)。Flurl提供了一种简单的方法来构建GET、POST、PUT等类型的请求,以及处理响应和异常。它还提供了一些高级功能,如链式调用、缓存请求结果、自动重定向等。本文将介绍Flurl的GET、POS

提升服务质量,群狼调研物业客户满意度调查来帮忙

在当今商业环境中,物业企业的持续发展离不开客户满意度调查这一重要环节。为何重视物业客户满意度调查?客户满意度是评估企业服务质量的重要指标,对于物业企业而言更是至关重要。通过调查客户的满意度,企业能够深入了解客户的需求和期望,发现问题所在,并做出相应的改进和调整,从而提升客户体验、增强客户忠诚度,进而促进企业的持续健康发

conan入门(二十七):因profile [env]字段废弃导致的boost/1.81.0 在aarch64-linux-gnu下交叉编译失败

今天在尝试用conan1.60.0使用aarch64-linux-gnu编译器交叉编译boost/1.81.0时报错了:conaninstallboost/1.81.0@-pr:haarch64-linux-gnu.jinja-pr:bdefault--buildboost输出如下:Configuration(prof

热文推荐