前端测试:Part II (单元测试)

原文:Testing Your Frontend Code: Part II (Unit Testing)html

By Gil Tayar前端

单元测试

咱们在Part1里已经说过,但与那测试就是测试单元的代码,无论这些单元是函数、模块仍是类。多数人认为测试应该以单测为主,但我不这么认为,若是你赞成也没有问题。我会一遍一遍又一遍地在这一系列文章中强调,你怎测试都行,只要你写了足够多的测试,让你对你的上线有信心就行。webpack

无论你写多少单测,单测确实是最好写也最好懂的测试,它们天生具备函数属性。设定一个单元的输入,执行,检查输出(输入可能就是一个函数的参数,输出就是返回值)。git

更重要的是你应该提醒本身写代码时要让这些单元彼此隔离,不会相互依赖,这样才能方便单测。github

计算器应用中的单元

理论已经够多了,咱们看下咱们的计算器,源码在这里。这是个React应用,有两个主要的组件,keypaddisplay。显然他们是单元且对其余单元无依赖。可是他们是React层面的单元,之后我会专门讲如何测它们。web

若是你已经阅读过代码可能会疑问为啥我不用JSX。缘由是我不想使用代码转译。Node和现代的浏览器可以识别ES6,因此为啥不让你写的代码直接运行呢?是的,个人代码不能再IE中运行,可是这只是个demo,因此没问题。在一个真实的项目里,我会添加代码转译的。算法

译注:最新的代码仓库已经用JSX重构并增长了babel转译,因此这段呵呵了。数据库

总有些代码负责处理当点击了一个数字或者运算符后的运算,哪部分代码完成这部分工做呢?按照如今时髦的作法,我也把个人组件分红了展现型组件(keypaddisplay)和状态型组件(calculator-app)。后一个组件时惟一一个有状态的组件,负责调用计算逻辑,并驱动display展现点击后应该显示啥。npm

calculator模块

负责计算的逻辑并不在组件中,它是一个单独的模块calculator,并不依赖React。这样的模块是最适合单测的。对UI和IO没有依赖的模块最适合单测。你应该尽力让你更多的业务逻辑以不依赖IO或者UI的形式写成模块。json

在前端里,不依赖IO是啥意思呢?不访问文件、数据库… ?不,前端里原本就没有这些。可是还会有Ajax,local storage,DOM 访问,浏览器API访问,对我而言,这些都是IO。

我是如何把计算器的逻辑和组件分开的?在这里,其实很简单,这些逻辑都是算法类的,我把它们放到了calculator模块中。

这个模块很是简单,输入是计算器的当前状态(一个对象)和一个字符(一个数字或者运算符),返回值是计算器的新状态。若是你用过Redux,这个逻辑跟Redux的Reducer差很少。可是如何获取最原始的状态呢?简单,这个模块一样Export了initialState,你能够用它去初始化计算器。计算器的状态并不是不透明的,它包含可一个组件display,用来将计算器的内在状态显示出来。

若是你没有耐心去仔细阅读代码,咱们这里只看下开头就行,这部分最重要,算法是怎么实现的其实可有可无。

module.exports.initialState = { display: '0', initial: true }

module.exports.nextState = (calculatorState, character) => {
  if (isDigit(character)) {
    return addDigit(calculatorState, character)
  } else if (isOperator(character)) {
    return addOperator(calculatorState, character)
  } else if (isEqualSign(character)) {
    return compute(calculatorState)
  } else {
    return calculatorState
  }
}

//....
复制代码

怎么测试呢?咱们一般会用一个测试框架。当下最流行的测试框架是Mocha,咱们就用它来测试。固然用Jest,Jasmine,Tape或者其余框架均可以。

用Mocha进行单测

全部的测试框架都是相似的——你把测试代码写成函数,测试框架负责执行它们。

npm installMoch后,咱们就能够经过npm脚本运行了。固然,命令就是"Mocha"。看下package.json,你会看到:

"scripts": {
...
    "test": "mocha 'test/**/test-*.js' && eslint test lib",
...
},
复制代码

运行npm test,就会执行以test打头的文件夹里的测试脚本。若是你clone了这个代码仓库,你要先npm install

(顺便说下,把测试脚本放在根目录下的test文件夹中是测试的惯例,若是你想要让别人认为你写测试很专业,那你也应该这么作)

执行后,输出长这个样子。

若是有一个测试没有经过,你会看到刺眼的红色,而后就能够立刻修改了。

看一下咱们的测试用例:

// test-calculator.js
const {describe, it} = require('mocha')
const {expect} = require('chai')
const calculator = require('../../lib/calculator')

describe('calculator', function () {
  const stream = (characters, calculatorState = calculator.initialState) =>
    !characters
      ? calculatorState
      : stream(characters.slice(1),
               calculator.nextState(calculatorState, characters[0]))

  it('should show initial display correctly', () => {
    expect(calculator.initialState.display).to.equal('0')
  })
  it('should replace 0 in initialState', () => {
    expect(stream('4').display).to.equal('4')
  })
//...
复制代码

咱们先引入mocha,还有它的断言库expect(稍后咱们会将啥事断言库)。引入一些咱们须要的函数describeit

而后引入咱们要测试的模块——calculator

而后就是使用it函数定义的测试用例了,以下:

it('should show initial display correctly', () => {
    expect(calculator.initialState.display).to.equal('0')
})
复制代码

it函数接受一个字符串参数,用来描述测试用例,另外一个参数是一个函数,就是测试自己了。可是it是不能“裸奔”的,须要被包裹在describe函数定义的测试组中。

在测试逻辑中写啥呢?其实啥均可以。在这里咱们就是判断了下初始展现的值是否是等于0. 若是不用expect,咱们能够这么写:

if (calculator.initialState.display !== '0')
  throw 'failed'
复制代码

Mocha中若是一个测试不经过,就会抛出一个异常,就是这么简单。可是使用expect让咱们可使用他的一些特性来方便地检查数组、对象的值。

这就是单元测试的主旨了——执行一个或者一组函数(若是你是面向对象测试,则一般实例化一个对象,而后调用它的方法),检查返回的结果是否等于预期结果。

编写可测试的代码

单测里最复杂的不是测试自己,而是分离代码,从而让它们尽量地变得可测。**可单测的代码就是,对其余模块和IO没有依赖的代码。**这并不简单,由于咱们一般习惯于讲业务逻辑和IO,UI耦合起来。可是这个目标仍然是能够达到的,有不少技术。例如,若是你有一段验证表单的代码,把它们分离出来变成一个一个的验证函数,而后对它们进行测试。

测试代码运行在Node环境下?

注意到很重要的一点——单测是运行在Node环境下的。即便计算器应用的代码是跑在浏览器中,咱们仍然使用Node去跑咱们的测试,包括要上线的代码。

这怎么能行呢?这是由于咱们的代码是同构的。这意味着它们能同时运行在浏览器和Node环境中。若是你的代码中没有任何IO操做,这就意味着它并非只能在浏览器中运行。尤为是,咱们的代码使用了require来组织代码,既能被NodeJS识别,又能被Webpack打包(看下package.json,你就会发现使用了webpack)。

"scripts": {
   "build": "webpack && cp public/* dist",
   ...
}
复制代码

在浏览器环境下单测

顺便提一下,咱们可使用karma来实如今浏览器中运行Mocha。可是我谨认为若是能在Node中运行就在Node中运行(如今你其实很容易写出在两端都能运行的代码),由于不管是运行仍是debug都更方便。不编译代码的话,运行起来会更快。

可是不在浏览器中跑测试,咱们就不能确认咱们的代码能在浏览器中运行。两个环境中的一些细微差异可能会致使一些问题。

下周

上面说的问题就是E2E测试要管的事了——在真实的浏览器环境中测试咱们的代码。下周咱们将如何写E2E测试。

()

相关文章
相关标签/搜索