Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从零开始做Vue前端架构(6)单元测试 & 代码覆盖率 #15

Open
CodeLittlePrince opened this issue Feb 1, 2018 · 1 comment

Comments

@CodeLittlePrince
Copy link
Owner

CodeLittlePrince commented Feb 1, 2018

新的一年

之前因为上家公司的经营出了问题,年前的大裁员,过了一个漫长的春节。
之后加入了新公司,然后正好赶上一个很紧急的项目,忙成狗,因此好久没更新文章了。
不过,我又回来啦!

前言

自动化测试,我们将使用karma和nightmare,内容会包括:

  1. 单元测试
  2. e2e测试(放下一篇文章)
    其实,单元测试一般用在写公共包的时候,比如通用的js函数库,通用的UI组件库。基本不太会在做业务项目的时候还使用单元测试。
    然后,e2e测试的话,那其实往往是测试工程师需要做的,往往使用selenium。
    那难道前端就不需要学测试了吗?
    答案肯定是否定的,不然我写个毛......
    vue的项目就用了单元测试和e2e。
    基于vue的UI组件库,比如:饿了么的element、滴滴的cube-ui、有赞的vant等都有单元测试(咩有e2e,因为没必要)。
    滴滴的话,我问了下黄轶大佬,他们项目前端自动化测试是用了单元测试和e2e的。
    总之,两种都是由应用场景的,e2e虽然用的不多,或者说有时候不是前端自动化的范畴,但是其实做起来很简单,学会准没错!

一、单元测试

安装karma

npm install karma --save-dev
npm install karma-jasmine karma-chrome-launcher jasmine-core --save-dev

package.jsonscripts配置
"test": "karma start"

初始化karma

$ karma init my.conf.js

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 a browser 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".
Press Enter to move to the next question.
> test/**/*.js
>

Should any of the files included by the previous patterns be excluded?
You can use glob patterns, eg. "**/*.swp".
Press Enter 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.
> yes

初始成功以后,会生成一份karma.conf.js配置文件。

顺便,在根目录创建一个test的文件夹,在这文件夹中创建一份index.js,内容为:

describe('A spec suite', function() {
  it('contains a passing spec', function() {
    console.log('Hello Karma')
  })
})

运行一下:
npm run test
我们会看到程序会自动打开chrome浏览器,然后显示测试结果。

正式的写个单元测试

重新组织test文件夹

.
└── unit
    ├── index.js
    ├── karma.conf.js
    └── specs
        └── dom.spec.js

因为我们还要做e2e测试,所以,在test下,用各个文件夹区分,unit下就是单元测试的内容了。

安装一系列包

karma-webpack
karma-sourcemap-loader
karma-coverage
chai
sinon
sinon-chai
karma-sinon-chai
karma-mocha-reporter

karma-webpack:因为我们的项目代码是用es6或者es7写的,所以webpack编译是必须的
karma-sourcemap-loader:sourcemap明显是必要的
karma-coverage:做代码覆盖率的时候需要用
chai:搭配mocha断言
sinon:搭配mocha做spy、stub、mock
sinon-chai:用chai来做sinon的断言,可以说是扩展
karma-sinon-chai:方便在测试代码中的调用,直接就能用expect、sinon.spy等,不需要每个文件都import
karma-mocha-reporter:mocha测试完的报告

像karma、mocha、chai、sinon这种测试工具,我不会很详细的介绍,全部都介绍的话内容实在有点多,而且也比较枯燥。想要学习可以看我的参考资料,是我花了大把时间筛选整理出来的。

修改karma.conf.js

const webpackConfig = require('../../webpack.config.test.js')

module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'sinon-chai'],

    // list of files / patterns to load in the browser
    files: [
      './index.js'
    ],

    // list of files / patterns to exclude
    exclude: [
    ],

    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {
      './index.js': ['webpack', 'sourcemap', 'coverage']
    },

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['mocha', 'coverage'],

    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    },
    .
    .
    .
    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: true,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity,

    webpack: webpackConfig,
    webpackMiddleware: {
      stats: 'errors-only'
    }
  })
}

karma原本在根目录,我们直接移过来就好了。然后修改的不多,我稍微解释一下:

  1. files:将要被测试的文件
  2. preprocessors:在引入文件前,需要用什么方式处理,我们看到了,包括webpack、sourcemap、coverage
  3. reporters:测试完成后的报告,我们需要mocha的报告和coverage的报告
  4. coverageReporter:代码覆盖率生成的报告文件地址和存在形式设置
  5. webpack:在这需要引入webpack的配置,我们见到顶部,引入了webpack.test.config.js文件,我们待会儿会介绍里面的配置
  6. webpackMiddleware:stats: 'errors-only'我们让webpack的编译过程不显示出来,除非编译报错

配置webpack.test.config.js

const webpackConfigBase = require('./webpack.config.base.js')

const config = Object.assign(webpackConfigBase.config, {
  // sourcemap 模式
  devtool: '#inline-source-map',
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
})

module.exports = config
``

#### 编辑index.js入口文件
这个文件是为了配合`karma-webpack`的,详情见[Alternative Usage](https://github.com/webpack-contrib/karma-webpack#alternative-usage)
```js
// require all test files (files that ends with .spec.js)
// 语法说明:https://doc.webpack-china.org/guides/dependency-management/#require-context
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)

// require all src files which in the app/common/js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../../app/common/js/', true, /\.js$/)
srcContext.keys().forEach(srcContext)

测试代码放在./specs文件夹下,被测试原文件在../../app/common/js/下。这里我只测试一些公共的js文件,如果你需要测试其它,可自行修改。比如一些基于vue的UI组件库,你想要测试所有组件代码,还需要做些配置上的修改,这方面可以参考滴滴的cube-ui项目,挺完整的,覆盖率也很高。

正式写测试代码

编辑dom.spec.js文件:

/**
 * 测试common/utils/dom.js
 */
import * as dom from 'common/js/utils/dom.js'

// const expect = require('chai').expect 装过sinon-chai就不需要这句了;sinon同理

describe('utils/dom', () => {
  // 测试hasClass
  it('hasClass', () => {
    const ele = document.createElement('div')
    ele.className = 'base kitty'
    // 是否含有base
    expect(dom.hasClass(ele, 'base')).to.be.equal(true)
    // 是否含有kitty
    expect(dom.hasClass(ele, 'kitty')).to.be.equal(true)
    // 是否含有tom
    expect(dom.hasClass(ele, 'tom')).to.be.equal(false)
    // 无参数
    expect(dom.hasClass()).to.be.equal(false)
  })
  // 测试addClass
  it('addClass', () => {
    const ele = document.createElement('div')
    ele.className = 'base'
    // 增加类名kitty
    dom.addClass(ele, 'kitty')
    expect(ele.className).to.be.equal('base kitty')
    // 再增加类名kitty,希望并不会有重复类名
    dom.addClass(ele, 'kitty')
    expect(ele.className).to.be.equal('base kitty')
  })
  // 测试removeClass
  it('removeClass', () => {
    const ele = document.createElement('div')
    ele.className = 'base kitty'
    // 删除类名kitty
    dom.removeClass(ele, 'kitty')
    expect(ele.className).to.be.equal('base')
    // 删除不存在的类名
    dom.removeClass(ele, 'tom')
    expect(ele.className).to.be.equal('base')
  })
  // 测试noce
  it('once', () => {
    const ele = document.createElement('div')
    const callback = sinon.spy()
    dom.once(ele, 'click', callback)
    // 点击一次
    ele.click()
    expect(callback).to.have.been.calledOnce
    // 再点击一次,预期应该是不调用callback的,所以依然为calledOnce
    ele.click()
    expect(callback).to.have.been.calledOnce
  })
})

代码注释已经很清楚啦~

运行测试

先修改下package.json配置:"test:unit": "karma start test/unit/karma.conf.js"
运行:

➜  construct git:(master) npm run test:unit

> [email protected] test:unit /Users/Terry/WFE/vue-study/construct
> karma start test/unit/karma.conf.js


START:
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.
ℹ 「wdm」: Compiling...
ℹ 「wdm」:
ℹ 「wdm」: Compiled successfully.
23 04 2018 01:25:39.438:INFO [karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
23 04 2018 01:25:39.440:INFO [launcher]: Launching browser Chrome with unlimited concurrency
23 04 2018 01:25:39.448:INFO [launcher]: Starting browser Chrome
23 04 2018 01:25:41.778:INFO [Chrome 66.0.3359 (Mac OS X 10.13.2)]: Connected on socket A9ZeKTNtnUU9MAceAAAA with id 51610088
  utils/dom
    ✔ hasClass
    ✔ addClass
    ✔ removeClass
    ✔ once

Finished in 0.008 secs / 0.004 secs @ 01:25:41 GMT+0800 (CST)

SUMMARY:
✔ 4 tests completed

=============================== Coverage summary ===============================
Statements   : 87.12% ( 142/163 ), 14 ignored
Branches     : 61.25% ( 49/80 ), 22 ignored
Functions    : 86.11% ( 31/36 ), 5 ignored
Lines        : 90.79% ( 138/152 )
================================================================================

参考资料

karma 测试框架的前世今生
karma thesis
karma 官网
前端自动化测试工具overview
前端自动化测试解决方案探析
mocha官网
代码测试覆盖率分析
聊一聊前端自动化测试
Sinon指南: 使用Mocks, Spies 和 Stubs编写JavaScript测试
sinon-chai github

论文是个很有意思的东西,看多了你会惊人地发现,很多大厂有深度的文章其实都是对论文的纯翻译~
另外还参考了vue和滴滴的cube-ui的项目测试部分。

项目完整代码

Vue前端架构-by 子咻

@CodeLittlePrince CodeLittlePrince changed the title 从零开始做Vue前端架构(6) 从零开始做Vue前端架构(6)单元测试 Apr 22, 2018
@CodeLittlePrince CodeLittlePrince changed the title 从零开始做Vue前端架构(6)单元测试 从零开始做Vue前端架构(6)单元测试 & 代码覆盖率 Apr 22, 2018
@UprototypeU
Copy link

膜拜大老,今天开始准备学习学习

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants