前端测试(上)

前端测试(上)

在前端项目中,前端测试并没有被重视,原因有很多,比如 学习/研发成本高团队不够重视,或者项目不合适等,在这里我们不去追究是什么原因导致这种现象,但是有一点我很确定,造成这种原因,还有一个更重要的原因,就是 “意识不到位”,即使有很多同学了解过单元测试,但是也不知道如何应用到 “项目” 中,针对这种现象,我们从一个简单却很常见的小项目,来打开测试工程化冰山一角

在刷题的过程中,我们经常会使用一个项目用于练习写笔试题,比如排序,查找之类的算法题目

石器时代

新建一个工程,目录如下

├── index.js
├── index.html
└── src
    └── search.js
  • index.html - 基于浏览器运行,至少早期的笔试题目,基于浏览器即可;此例,引入search.jsindex.js即可
  • index.js - 用于写测试代码,比如 console.log(binarySearch([1],1)===0)
  • search.js - 用于写查找相关的算法代码,比如顺序查找、插值查找等,在这里我只写二分法查找顺序查找函数
/**
 * 二分法
 * @param {Array} arr 
 */
function binarySearch(arr, expected) {
  let start = 0
  let end = arr.length - 1
  while (start <= end) {
    let mid = parseInt(start + (end - start) / 2)
    let value = arr[mid]
    if (value === expected) {
      return mid
    } else if (value > expected) {
      end = mid
    } else {
      start = mid
    }
  }
  return -1
}
/**
 * 顺序查找
 * @param {*} arr 
 * @param {*} expected 
 */
function sequentialSearch(arr, expected) {
  let i = 0
  while (i > arr.length) {
    let value = arr[i]
    if (value === expected) {
      return i
    }
    i++
  }
  return -1
}

OK,大功告成,把页面拖到浏览器中直接运行,连服务器都省了~~!
当我准备为我这个完美的项目鼓掌的时候,眼角瞟到我的座右铭,成为一个专业的大前端,此时此刻 专业这个词格外刺眼,作为新世纪好青年,我怎么可能让别人质疑我的专业,于是我要继续装(入)逼(坑)

青铜时代

  1. 使用npm创建工程,并且对模块进行管理
  2. 使用git对项目仓库以及迭代进行管理
  3. 载体nodejs环境代替浏览器环境,这样可以测试文件读取等IO操作
.
├── node_modules
└── test
    └── test.js
└── src
    └── search.js
├── package.json
├── .gitignore
├── index.js    

在 package.json 配置

{
    ....
    "scripts":{
        "test":"node test/test.js"
    }
}

对应 js的模块 要改成commonjs规范
search.js 调整

function binarySearch(){
    //todo
}
function sequentialSearch(){
    //todo
}
module.exports = {
  binarySearch,
  sequentialSearch
}

index.js 调整

const { binarySearch,sequentialSearch } = require('./src/search')
module.exports = {
  binarySearch,
  sequentialSearch
}

test.js 调整,为了让提示更加明显点,我们尝试让描述更加丰富点

const { binarySearch,sequentialSearch } = require('../index')
console.log(‘二分查找: [1]的1在数组0位置上’,binarySearch([1],1)===0)
console.log(‘二分查找:[1,2,3]的1在数组0位置上’,binarySearch([1,2,3],1)===0)
console.log(‘二分查找:[1,2,3]的2在数组1位置上’,binarySearch([1,2,3],2)===0)

console.log(‘顺序查找:[1]的1在数组0位置上’,sequentialSearch([1],1)===0)
console.log(‘顺序查找:[1,2,3]的1在数组0位置上’,sequentialSearch([1,2,3],1)===0)
console.log(‘顺序查找:[1,2,3]的2在数组1位置上’,sequentialSearch([1,2,3],2)===0)

一顿操作猛如虎之后,感觉完美的一笔~~
我迫不及待,运行 npm run test

二分查找:[1]的1在数组0位置上 true
二分查找:[1,2,3]的1在数组0位置上 true
二分查找:[1,2,3]的2在数组1位置上 false
顺序查找:[1]的1在数组0位置上 false
顺序查找:[1,2,3]的1在数组0位置上 false
顺序查找:[1,2,3]的2在数组1位置上 false

我们发现 有几点不足

  1. 当测试用例增加时,测试代码变的难以管理,所有测试输出揉在一起,没有分组的管理
  2. 不管成功或者失败,没有高亮显示,也没有颜色上的区分
  3. 即使错误,也没有把错误详细打印出来
  4. 没有相关输出报表

黄金时代

为了解决 青铜时代 遗留下不少体验问题,我们不得不封装一些方法,强化console的输出,文档输出,可视化等输出,然而我们所做的一切强化,都是新概念 测试框架的雏形,不过在正式介绍 测试框架前,我们先了解下 断言

“我×,测试框架?断言?这尼玛又是什么?”

断言是单元测试中用来保证最小单元是否正常的检测方法,用于判断逻辑执行是否达到开发者预期的表达式,断言在运行的过程中,若断言不为真,程序会中止运行

“常用的断言库有哪些?”
  • assert - nodejs的内置核心模块,node环境可以直接使用
  • shouldjs - 基于assert模块进行封装扩展
  • expectjs - 基本是 shouldjs 的缩水版
  • chai - 目前比较流行的断言库,支持 TDD(assert),BDD(expect、should)两种风格

我们先简单学习 assert, 作为Nodejs内置核心模块,无需引用,最为 断言 入门库最为合适

## assert

var assert=require('assert')
assert.equal(Math.max(1,100),100)

一旦 assert.equal()不满足期望,将会抛出AssertionError异常,整个程序将会停止运行

常用的检测方法
  • ok(actual) - 判断结果是否为真
  • strictEqual(actual,expected,[,message]) - 判断实际值和期望值是否严格相等
  • deepStrictEqual(actual, expected[, message]) -判断实际值和期望值是否深度严格相等
  • doesNotReject(asyncFn, error) - 判断代码块是否返回reslove
  • rejects(block, error)- 判断结果返回reject
  • throws(block, error)- 判断结果是否抛出异常
  • ifError()- 判断实际值是否为一个假值(null,undefined,0,'',false);如果为真值,就会抛出异常
  • fail([message]) - 直接报错
“感觉脑壳疼,能不能通俗点?”
先来一个例子,压压惊,我们把青铜时代的代码优化下
 console.log(‘顺序查找:[1]的1在数组0位置上’,sequentialSearch([1],1)===0)
 //为了通用性,我们把sequentialSearch([1],1)===0 提炼出来
 function equal(actual,expected,message){
    return actual===expected?message:`${actual}!==${expected}`
 }
  console.log(‘顺序查找:[1]的1在数组0位置上’,equal(sequentialSearch([1],1),0,'成功'))

通俗的说 就是 equal这个方法就是断言

“我迷迷糊糊的貌似明白了一点,那我运行一下尝尝鲜吧”

test/index.js

const assert = chai.assert
const { binarySearch } = require('../index')

assert.equal(binarySearch([1], 1), 0)//成功
assert.equal(binarySearch([1], 1), 1)//失败
assert.equal(binarySearch([1,2], 2), 1)//成功
assert.equal(binarySearch([1,2], 1), 0)//失败

运行 node test/index.js

//失败输出
AssertionError: expected 0 to equal 1
    at Object. (F:\learn\test\index.js:19:8)
“呃....我觉得这体验,也青铜时代差不多”

我们可以看到,在第二个测试用例执行时,发现代码执行失败后,直接退出程序,同时提示你 期望值实际运行值,以及对于错误代码相关提示等等。错误提示方面比封装equire方法强大不少;但是,依旧不能让我愿意使用它。

  1. 成功时,没有任何提示
  2. 虽然有错误提示,但是运行到第一个错误的时候,就程序退出;开发者无法看到 自己的测试用例 错误多少个
  3. 依旧没有高亮,可视化方面依旧苍白

没错,断言拿到非常重要错误信息;但是他没有解决体验问题;如果说 断言是里子,那测试框架 就是面子

“测试框架是什么?”

测试框架 通俗的说就是专门 服务于代码块测试解决方案,他主要有以下功能

  • 管理测试用例
  • 生成测试报告
“常用的测试框架有哪些?”
  • [jasmine]() -自带断言(assert),mock 功能
  • [mocha]() -框架不带断言和mock功能,需要结合其他工具

通俗的说,测试框架 就是 管理/执行断言,他和断言一起使用将会更加强大

Mocha

mocha 是一款强大的测试框架,能够运行在nodejs和浏览器中,能够高效的管理测试用例,支持多种测试报告格式

  • 支持多种断言库:chai/shouldjs/expectjs/assert
  • 支持两种测试风格:TDD/BDD

    • TDD:基于测试用例 进行测试
    • BDD:基于产品本身功能 进行测试
常用方法
  1. describe(string,callback) -主要用于对测试用例的分组,层级描述。TDD使用suite
  2. it(string [,callback])-测试用例,callback 包含一个或者多个断言;当callback不存在时,表示这个测试用例需要写,但目前还未写入,状态使用pending表示
  3. hook-用于协助describe中测试用例的准备·安装·卸载和回收等工作,Hook一般用于describe内,但也可以describe外,作为顶级Hook

    • before/after([ string ,]callback) - 分别在进入或者退出describe时触发执行
    • beforeEach/afterEach([ string ,]callback) - 分别在describe中每个测试用例执行前和执行后触发执行

Full example
test/index.js


describe('hooks', function() {
  before(function() {
    console.log('before')
  });

  after(function() {
    console.log('after')
  });

  beforeEach(function() {
    console.log('beforeEach')
  });

  afterEach(function() {
    console.log('afterEach')
  });
  it('Test1',()=>{
    console.log('test1')
  })
  it('Test2',()=>{
    console.log('test2')
  })
  // test cases
});

运行 npm run test

{
    "script":{
        " test":"mocha"
    }
}

hooks
before
beforeEach
test1

√ Test1

afterEach
beforeEach
test2

1) Test2

afterEach
after

1 passing (15ms)
1 failing

1) hooks

   Test2:

  AssertionError: expected 0 to equal 1
  + expected - actual

  -0
  +1

  at Context.it (test\index.js:93:12)
我们可以看到 基于mocha后的断言,他的提示体验大大的提升
- 成功后,有相关提示
- 遇到失败时,依旧可以执行下去,展示所有失败用例信息
- 统计测试用例成功数和失败数
4. **异步处理** 
      - done
  it('should save without error', (done)=> {
    var user = new User('Luna');
    user.save((err)=> {
      if (err) done(err);
      else done();
    });
    //user.save(done);
  });

  ```
  - promise
  ```ts
  it('respond with matching records', ()=> {
    return db.find({type: 'User'}).should.eventually.have.length(3);
  });
  ```
  - async/await
  ```ts
  it('responds with matching records', async function() {
    const users = await db.find({type: 'User'});
    users.should.have.length(3);
  });
  ```
    1. Only-屏蔽其他测试单元/测试用例,只执行标识为Only的测试单元/用例。一般用于 当你的单元测试越写越多时,只想测试新写的单元测试是否正确,这个属性就可以帮你在执行时,帮你过滤掉其他测试,加快执行速度

      describe.only('something', function() {
        // 只会跑包在里面的测试
      })

      或者

      it.only('do do', () => {
        // 只会跑这一个测试
      })
    1. skip-表示执行时,跳过标识的测试单元/测试用例,可以作用于describeit

      it.skip('should return -1 unless present', function() {
        // 代码不存被执行
      });
      
      it('should return the index when present', function() {
        // 代码会执行
      });

      可以this.skip()在测试用例执行的时候,根据运行时过滤当前测试案例

      
      describe('outer', function() {
        before(function() {
          this.skip();
        });
      
        after(function() {
          // will be executed
        });
      
        describe('inner', function() {
          before(function() {
            // will be skipped
          });
      
          after(function() {
            // will be skipped
          });
        });
      });
    2. 其他
    • timeout - 超时

    你可能感兴趣的:(assert.js,mocha)