一步一步实现现代前端单元测试

2年前写过一篇文章用Karma和QUnit作前端自动单元测试,只是大概讲解了 karma 如何使用,针对的测试状况是传统的页面模式。本文中题目中【现代】两字代表了这篇文章对比以前的最大不一样,最近几年随着SPA(Single Page Application) 技术和各类组件化解决方案(Vue/React/Angular)的普及,咱们开发的应用的组织方式和复杂度已经发生了翻天覆地的变化,对应咱们的测试方式也必须跟上时代的发展。如今咱们一步一步把各类不一样的技术结合一块儿来完成页面的单元测试和 e2e 测试。javascript

1 karma + mocha + power assert

  • karma - 是一款测试流程管理工具,包含在执行测试前进行一些动做,自动在指定的环境(能够是真实浏览器,也能够是 PhantamJS 等 headless browser)下运行测试代码等功能。
  • mocha - 测试框架,相似的有 jasminejest 等。我的感受 mocha 对异步的支持和反馈信息的显示都很是不错。
  • power assert - 断言库,特色是 No API is the best API。错误显示异常清晰,自带完整的自描述性。
    1) Array #indexOf() should return index when the value is present:
       AssertionError: # path/to/test/mocha_node.js:10
    
    assert(ary.indexOf(zero) === two)
           |   |       |     |   |
           |   |       |     |   2
           |   -1      0     false
           [1,2,3]
    
    [number] two
    => 2
    [number] ary.indexOf(zero)
    => -1
    复制代码

如下全部命令假设在 test-demo 项目下进行操做。css

1.1 安装依赖及初始化

# 为了操做方便在全局安装命令行支持
~/test-demo $ npm install karma-cli -g

# 安装 karma 包以及其余须要的插件和库,这里不一一阐述每一个库的做用
~/test-demo $ npm install karma mocha power-assert karma-chrome-launcher karma-mocha karma-power-assert karma-spec-reporter karma-espower-preprocessor cross-env -D

# 建立测试目录
~/test-demo $ mkdir test

# 初始化 karma
~/test-demo $ karma init ./test/karma.conf.js
复制代码

执行初始化过程按照提示进行选择和输入html

Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mocha

Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no

Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>

What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>

Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> no
复制代码

生成的配置文件略做修改,以下(因篇幅缘由,隐藏了注释):前端

module.exports = function(config) {
  config.set({
    basePath: '',

    // 表示能够在测试文件中不需引入便可使用两个库的全局方法
    frameworks: ['mocha', 'power-assert'],
    files: [
      '../src/utils.js',
      './specs/utils.spec.js.js'
    ],
    exclude: [
    ],
    preprocessors: {
      './specs/utils.spec.js': ['espower']
    },
    reporters: ['spec'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['Chrome'],
    singleRun: false,
    concurrency: Infinity
  })
}
复制代码

1.2 待测试代码

咱们把源文件放在src目录下。vue

// src/utils.js
function reverseString(string) {
  return string.split('').reverse().join('');
}
复制代码

1.3 测试代码

测试代码放在test/specs目录下,每一个测试文件以 .spec.js 做为后缀。java

// test/spes/utils.spec.js
describe('first test', function() {
  it('test string reverse => true', function() {
    assert(reverseString('abc') === 'cba');
  });

  it('test string reverse => false', function() {
    assert(reverseString('abc') === 'cba1');
  });
});
复制代码

1.4 运行测试

回到项目根目录,运行命令 npm run test 开始执行测试,而后看到浏览器会自动打开执行测试,命令行输出结果以下:node

[karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
[launcher]: Launching browser Chrome with unlimited concurrency
[launcher]: Starting browser Chrome
[Chrome 63.0.3239 (Mac OS X 10.13.1)]: Connected on socket HEw50fXV-d24BZGBAAAA with id 24095855

  first testtest string reverse => truetest string reverse => false
	AssertionError:   # utils.spec.js:9

	  assert(reverseString('abc') === 'cba1')
	         |                    |
	         "cba"                false

	  --- [string] 'cba1'
	  +++ [string] reverseString('abc')
	  @@ -1,4 +1,3 @@
	   cba
	  -1

Chrome 63.0.3239 (Mac OS X 10.13.1): Executed 2 of 2 (1 FAILED) (0.022 secs / 0.014 secs)
TOTAL: 1 FAILED, 1 SUCCESS
复制代码

能够看出一个测试成功一个测试失败。webpack

2 测试覆盖率(test coverage)

测试覆盖率是衡量测试质量的主要标准之一,含义是当前测试对于源代码的执行覆盖程度。在 karma 中使用 karma-coverage 插件便可输出测试覆盖率,插件底层使用的是 istanbulgit

~/test-demo $ npm i karma-coverage -D
复制代码

修改 karma.conf.js 文件:github

preprocessors: {
  '../src/utils.js': ['coverage'],
  './specs/utils.spec.js': ['espower']
},

reporters: ['spec', 'coverage'],
coverageReporter: {
  dir: './coverage', // 覆盖率结果文件放在 test/coverage 文件夹中
  reporters: [
    { type: 'lcov', subdir: '.' },
    { type: 'text-summary' }
  ]
},
复制代码

再次运行测试命令,在最后会输出测试覆盖率信息

=============================== Coverage summary ===============================
Statements   : 100% ( 2/2 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 1/1 )
Lines        : 100% ( 2/2 )
================================================================================
复制代码

打开 test/coverage/lcov-report/index.html 网页能够看到详细数据

coverage.gif

3 webpack + babel

上面的例子,只能用于测试使用传统方式编写的 js 文件。为了模块化和组件化,咱们可能会使用ES6commonjsAMD等模块化方案,而后使用 webpack 的 umd 打包方式输出模块以兼容不一样的使用方式。通常咱们还须要使用ES6+的新语法,须要在 webpack 中加入babel做为转译插件。

webpack 和 babel 的使用以及须要的依赖和配置,这里不作详细说明,由于主要是按照项目须要走,本文仅指出为了测试而须要修改的地方

3.1 安装依赖

~/test-demo $ npm i babel-plugin-istanbul babel-preset-power-assert karma-sourcemap-loader karma-webpack -D
复制代码

3.2 修改配置

.babelrc

power-assert以及coverage的代码注入修改成在babel编译阶段进行,在.babelrc 文件中加入如下配置:

{
  "env": {
    "test": {
      "presets": ["env", "babel-preset-power-assert"],
      "plugins": ["istanbul"]
    }
  }
}
复制代码

test/index.js

在测试文件以及源码文件都很是多的状况下,或者咱们想让咱们的测试代码也使用上ES6+的语法和功能,咱们能够创建一个入口来统一引入这些文件,而后使用 webpack 处理整个入口,在test目录下新建index.js

// require all test files (files that ends with .spec.js)
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)

// require all src files except main.js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)
复制代码

karma.conf.js  修改已经增长对应的配置

{
  files: [
    './index.js'
  ],
  preprocessors: {
    './index.js': ['webpack', 'sourcemap'],
  },
  webpack: webpackConfig,
  webpackMiddleware: {
    noInfo: false
  },
}
复制代码

utils.spec.js

import reverseString from '../../src/utils';

describe('first test', function() {
  it('test string reverse => true', function() {
    assert(reverseString('abc') === 'cba');
  });

  it('test string reverse => false', function() {
    assert(reverseString('abc') === 'cba1');
  });
});
复制代码

3.3 运行测试

运行测试,能获得和第二步相同的结果。

4 vue

若是项目中使用了 vue,咱们想对封装的组件进行测试,也很是简单。

首先 webpack 配置中添加处理 vue 的逻辑,安装须要的依赖,这里再也不赘述。

src目录下添加HelloWorld.vue

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h2>Essential Links</h2>
    
  </div>
</template>

<script> export default { name: 'HelloWorld', data () { return { msg: 'Welcome to Your Vue.js App' } } } </script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped> h1, h2 { font-weight: normal; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
复制代码

而后添加测试代码:

// test/specs/vue.spec.js
import Vue from 'vue';
import HelloWorld from '@/HelloWorld';

describe('HelloWorld.vue', () => {
  it('should render correct contents', () => {
    const Constructor = Vue.extend(HelloWorld)
    const vm = new Constructor().$mount()
    assert(vm.$el.querySelector('.hello h1').textContent === 'Welcome to Your Vue.js App')
  })

})
复制代码

运行测试,能够看到命令行输出:

first testtest string reverse => truetest string reverse => false
        AssertionError:   # test/specs/utils.spec.js:9

          assert(reverseString('abc') === 'cba1')
                 |                    |
                 "cba"                false

          --- [string] 'cba1'
          +++ [string] reverseString('abc')
          @@ -1,4 +1,3 @@
           cba
          -1

  HelloWorld.vue
    ✓ should render correct contents
复制代码

这里 Vue 能替换为其余任意的前端框架,只须要按照对应框架的配置能正确打包便可。

结语

上面全部代码都放在了这个项目,能够把项目下载下来手动执行查看结果。

以上大概讲解了现代前端测试的方法和过程,可是有人会问,咱们为何须要搞那么多事情,写那么多代码甚至测试代码比真实代码还要多呢?这里引用 Egg 官方一段话回答这个问题:

先问咱们本身如下几个问题:
  - 你的代码质量如何度量?  
  - 你是如何保证代码质量?  
  - 你敢随时重构代码吗?  
  - 你是如何确保重构的代码依然保持正确性?  
  - 你是否有足够信心在没有测试的状况下随时发布你的代码?  

若是答案都比较犹豫,那么就证实咱们很是须要单元测试。  
它能带给咱们不少保障:  
  - 代码质量持续有保障  
  - 重构正确性保障  
  - 加强自信心  
  - 自动化运行   

Web 应用中的单元测试更加剧要,在 Web 产品快速迭代的时期,每一个测试用例都给应用的稳定性提供了一层保障。 API 升级,测试用例能够很好地检查代码是否向下兼容。 对于各类可能的输入,一旦测试覆盖,都能明确它的输出。 代码改动后,能够经过测试结果判断代码的改动是否影响已肯定的结果。
复制代码

是否是消除了不少心中的疑惑?

以上内容若有错漏,或者有其余见解,请留言共同探讨。


版权声明:原创文章,欢迎转载,交流 ,请注明出处“本文发表于xlaoyu.info“。

相关文章
相关标签/搜索