vue/cli4 单元测试与覆盖率体系搭建

探索 vue-cli4 创建集成 mocha + chai 的项目中,如何写单元测试和查看单元测试覆盖率。

1、建立一个集成单元测试的项目

首先创建一个新的项目 vue-cli4-unit-mocha-chai-nyc:

vue create vue-cli4-unit-mocha-chai-nyc

# 选择手动配置
? Please pick a preset: 
  zcloud (router, vuex, less, babel, eslint, unit-jest) 
  default (babel, eslint) 
❯ Manually select features 

# 选择需要的功能
? Please pick a preset: Manually select features
? Check the features needed for your project: 
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◉ Unit Testing
❯◉ E2E Testing

# 使用 history 模式
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

# scss
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): 
  Sass/SCSS (with dart-sass) 
❯ Sass/SCSS (with node-sass) 
  Less 
  Stylus   

# esLint
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: 
  ESLint with error prevention only 
  ESLint + Airbnb config 
❯ ESLint + Standard config 
  ESLint + Prettier 

# 保存时进行检测
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯◉ Lint on save
 ◯ Lint and fix on commit
 
 # 单元测试 mocha + chai
 ? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: (Use arrow keys)
❯ Mocha + Chai 
  Jest 
  
  # 端到端测试
  ? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: (Use arrow keys)
❯ Cypress (Chrome only) 
  Nightwatch (WebDriver-based) 

# 单独的配置文件
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, ESLint, etc.? 
❯ In dedicated config files 
  In package.json 

# 不保存配置
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass)
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Mocha
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? (y/N) n

# 开始创建项目
Vue CLI v4.1.2
✨  Creating project in /Users/heyi/File/Front-End/demo/vue-unit.
  Initializing git repository...
⚙  Installing CLI plugins. This might take a while...

···

  Generating README.md...

  Successfully created project vue-cli4-unit-mocha-chai-nyc.
  Get started with the following commands:

 $ cd vue-cli4-unit-mocha-chai-nyc
 $ npm run serve

vue/cli4 单元测试与覆盖率体系搭建_第1张图片
创建项目完成后,可以发现 cli4 会自动生成一个默认的单元测试例子 tests/unit/ example.spec.js,其内容如下:

import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  it('renders props.msg when passed', () => {
    const msg = 'new message'
    const wrapper = shallowMount(HelloWorld, {
      propsData: { msg }
    })
    expect(wrapper.text()).to.include(msg)
  })
})

接下来,进入项目并运行单元测试:

cd vue-cli4-unit-mocha-chai-nyc
npm run test:unit

> [email protected] test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit

 WEBPACK  Compiling...

  [===                      ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 3019ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 3019ms

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed


  1 passing (26ms)

 MOCHA  Tests completed successfully

npm run test:unit 命令会自动读取 tests/unit/ 目录下的 *.spec.js 文件,来跑我们的单元测试。

可以发现,通过 vue-cli4 创建集成的单元测试项目,已经可以让我们做到零配置来写单元测试了。接下来就动手写一个单元测试试试。

2、动手写第一个单元测试


首先我们在 src/components/ 目录下创建文件 AppEmpty.vue,其内容如下:







接着在 tests/unit/ 目录下创建 app-empty.spec.js 文件,并添加如下内容:

import { expect } from 'chai'
import { shallowMount } from '@vue/test-utils'
import AppEmpty from '@/components/AppEmpty.vue'

describe('AppEmpty.vue', () => {
  it('no props.text when passed', () => {
    const wrapper = shallowMount(AppEmpty)
    expect(wrapper.text()).to.include('no data')
  })

  it('renders props.text when passed', () => {
    const text = '暂无数据'
    const wrapper = shallowMount(HelloWorld, {
      propsData: { text }
    })
    expect(wrapper.text()).to.include(text)
  })
})


最后,再次运行 test:unit 命令:

npm run test:unit

> [email protected] test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit

 WEBPACK  Compiling...

  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 1935ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 1935ms

 MOCHA  Testing...



  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed

  HelloWorld.vue
    ✓ renders props.msg when passed


  3 passing (35ms)

 MOCHA  Tests completed successfully


可以看到,我们自己写的单元测试也跑了起来。

3、单元测试文件书写位置调整


如果所有的 *.spec.js 文件都写在 tests/unit/ 目录下会显得很凌乱,那么如果我们在 /unit 目录下再新建文件夹,会被读取到吗?答案是肯定的。


在 tests/unit/ 目录下新建一个 components 文件夹,把刚刚创建的 app-empty.spec.js 文件移进去。再次运行 test:unit 命令:

npm run test:unit

> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit

 WEBPACK  Compiling...

  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 2035ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 2035ms

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed

  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed


  3 passing (57ms)

 MOCHA  Tests completed successfully


说明我们是可以自己建立文件夹进行整理我们的单元测试文件的。


如果我们的项目是一个去中心化的架构,我们可能希望我们的单元测试文件位于我们的组件旁边,而不是都写在 tests/unit/ 目录下。


如何实现呢?


在 tests/unit 目录下新建 index.spec.js 文件,其内容如下:

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


就可以在 src/ 目录下任何位置写我们的单元测试文件了。


为了验证所言非虚,简单的验证一下。复制 tests/unit/components/app-empty.spec.js 文件,在 src/components/ 目录下粘贴,然后稍稍修改一下测试套件的文本:

- describe('AppEmpty.vue', () => {
+ describe('src/**/*.spec.js AppEmpty.vue', () => {


再次运行 test:unit 命令:

npm run test:unit

> [email protected] test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit

 WEBPACK  Compiling...

  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 1968ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 1968ms

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed

  src/**/*.spec.js AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed

  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed


  5 passing (38ms)

 MOCHA  Tests completed successfully


嗯,所言不虚。

4、使用 mochawesome 输出结果


到目前为止,我们的测试报告都是在命令行展示,那么如果我们想要把结果输出并展示在页面上如何做到呢?


首先在项目中安装 mochawesome:

npm install --save-dev mochawesome


然后在 package.json 中修改 test:unit 命令如下:

"test:unit": "vue-cli-service test:unit --reporter=mochawesome",


然后再次执行:

npm run test:unit                      

> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit --reporter=mochawesome

 WEBPACK  Compiling...

  [===                      ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
  [=========================] 98% (after emitting)

 WARNING  Compiled with 1 warnings

 warning  in ./src/components/app-empty.spec.js

Module Error (from ./node_modules/eslint-loader/index.js):

/Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
   5:1  error  'describe' is not defined  no-undef
   6:3  error  'it' is not defined        no-undef
  11:3  error  'it' is not defined        no-undef

✖ 3 problems (3 errors, 0 warnings)


 @ ./src sync \.spec.js$
 @ ./tests/unit/index.spec.js
 @ ./node_modules/mochapack/lib/entry.js

  [=========================] 100% (completed)

 WEBPACK  Compiled with 1 warning(s)

Warning in ./src/components/app-empty.spec.js

  Module Error (from ./node_modules/eslint-loader/index.js):
  
  /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
     5:1  error  'describe' is not defined  no-undef
     6:3  error  'it' is not defined        no-undef
    11:3  error  'it' is not defined        no-undef
  
  ✖ 3 problems (3 errors, 0 warnings)
  

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed

  src/**/*.spec.js AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed

  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed


  5 passing (35ms)

[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json

[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html


非常醒目的警告提示首先被发现:

 WEBPACK  Compiled with 1 warning(s)

Warning in ./src/components/app-empty.spec.js

  Module Error (from ./node_modules/eslint-loader/index.js):
  
  /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/src/components/app-empty.spec.js
     5:1  error  'describe' is not defined  no-undef
     6:3  error  'it' is not defined        no-undef
    11:3  error  'it' is not defined        no-undef
  
  ✖ 3 problems (3 errors, 0 warnings)


告诉我们在 ./src/components/app-empty.spec.js 文件中 describe、it 未定义,为什么上面我们执行的时候都没有这个警告,这次就有了呢?而且,为什么只有 src/ 目录下的单元测试文件报了,tests/unit/ 目录下的没有报呢?


答案在项目的 .eslintrc.js 配置文件里:

module.exports = {
  root: true,
  env: {
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard'
  ],
  parserOptions: {
    parser: 'babel-eslint'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
  },
  overrides: [
    {
      files: [
        '**/__tests__/*.{j,t}s?(x)',
        '**/src/**/*.spec.{j,t}s?(x)'
      ],
      env: {
        mocha: true
      }
    }
  ]
}


在项目生成的 eslint 配置文件中,存在针对特定文件的配置,具体理解不细说,只需要简单的在其中加入 src/ 的配置即可:

···

  overrides: [
    {
      files: [
        '**/__tests__/*.{j,t}s?(x)',
+       '**/tests/unit/**/*.spec.{j,t}s?(x)',
        '**/src/**/*.spec.{j,t}s?(x)'
      ],
      env: {
        mocha: true
      }
    }
  ]
  
···


接下来我们再次运行 test:unit 命令,上面出现的告警将不复存在:

npm run test:unit

> [email protected] test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> vue-cli-service test:unit --reporter=mochawesome

 WEBPACK  Compiling...

  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 1212ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 1212ms

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed

  src/**/*.spec.js AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed

  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed


  5 passing (36ms)

[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json

[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html

 MOCHA  Tests completed successfully


处理了告警问题,回到最初的目的。


命令执行完成后,会在项目根目录下增加一个名为 mochawesome-report 的文件夹,里面就放着输出的单元测试报告。


我们在浏览器打开 mochawesome.html 可以看到如下页面:

vue/cli4 单元测试与覆盖率体系搭建_第2张图片

单元测试写了,测试报告也有了,接下来就差扫描项目单元测试覆盖率了。

5、使用 nyc 扫描测试覆盖率


文档地址:https://istanbul.js.org/


项目地址:https://github.com/istanbuljs/nyc


首先,在项目中安装所需依赖:

npm i --save-dev babel-plugin-istanbul istanbul-instrumenter-loader nyc


接着在项目根目录下新建 nyc.config.js 文件,其内容如下:

// 探索istanbul/nyc代码覆盖工具的原理 https://zhuanlan.zhihu.com/p/88524418
module.exports = {
  'check-coverage': true,
  'per-file': true,
  'lines': 0,
  'statements': 0,
  'functions': 0,
  'branches': 0,
  'include': [
    'src/**/*.js',
    'src/**/*.vue'
  ],
  'exclude': [
    'src/abandon-ui/**',
    'src/*.js',
    '**/*.spec.js'
  ],
  'reporter': [
    'lcov',
    'text',
    'text-summary'
  ],
  'extension': [
    '.js',
    '.vue'
  ],
  'cache': true,
  'all': true
}


完成后,在项目根目录新建 vue.config.js, 其内容如下:

const path = require('path')
const testMode = process.env.NODE_ENV === 'test'

module.exports = {
  lintOnSave: false,
  productionSourceMap: false,

  chainWebpack: config => {

    if (testMode) {
      config.merge({
        devtool: 'eval'
      })
      config.module
        .rule('istanbul')
        .test(/\.(js|vue)$/)
        .include
        .add(path.resolve(__dirname, '/src'))
        .end()
        .use('istanbul-instrumenter-loader')
        .loader('istanbul-instrumenter-loader')
        .options({ esModules: true })
        .before('babel-loader')
    }
  }
}


完成后,打开项目根目录下的 babel.config.js 文件,修改如下:

+ const testMode = process.env.NODE_ENV === 'test'
+
  module.exports = {
-   presets: ['@vue/cli-plugin-babel/preset']
+   presets: ['@vue/cli-plugin-babel/preset'],
+   plugins: testMode ? ['babel-plugin-istanbul'] : []
  }


最后,打开 package.json 文件,修改 test:unit 命令:

-    "test:unit": "vue-cli-service test:unit --reporter=mochawesome",
+    "test:unit": "nyc vue-cli-service test:unit --reporter=mochawesome",


再次执行新的 test:unit 命令:

npm run test:unit

> vue-cli4-unit-mocha-chai-nyc@0.1.0 test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc
> nyc vue-cli-service test:unit --reporter=mochawesome

 WEBPACK  Compiling...

  [===                      ] 10% (building)Download the Vue Devtools extension for a better development experience:
https://github.com/vuejs/vue-devtools
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
  [=========================] 98% (after emitting)

 DONE  Compiled successfully in 2210ms

  [=========================] 100% (completed)

 WEBPACK  Compiled successfully in 2210ms

 MOCHA  Testing...



  HelloWorld.vue
    ✓ renders props.msg when passed (41ms)

  src/**/*.spec.js AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed

  AppEmpty.vue
    ✓ no props.text when passed
    ✓ renders props.text when passed


  5 passing (72ms)

[mochawesome] Report JSON saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.json

[mochawesome] Report HTML saved to /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-nyc/mochawesome-report/mochawesome.html

 MOCHA  Tests completed successfully

-----------------|---------|----------|---------|---------|-------------------
File             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------------|---------|----------|---------|---------|-------------------
All files        |   16.67 |      100 |      50 |   16.67 |                   
 components      |     100 |      100 |     100 |     100 |                   
  AppEmpty.vue   |     100 |      100 |     100 |     100 |                   
  HelloWorld.vue |       0 |        0 |       0 |       0 |                   
 router          |       0 |      100 |       0 |       0 |                   
  index.js       |       0 |      100 |       0 |       0 | 5-23              
 store           |       0 |      100 |     100 |       0 |                   
  index.js       |       0 |      100 |     100 |       0 | 4                 
-----------------|---------|----------|---------|---------|-------------------

=============================== Coverage summary ===============================
Statements   : 16.67% ( 1/6 )
Branches     : 100% ( 0/0 )
Functions    : 50% ( 1/2 )
Lines        : 16.67% ( 1/6 )
================================================================================


执行成功后会发现根目录下除了新增加的 mochawesome-report/ 目录,还多了 coverage/ 和 .nyc_output/ 目录,我们的覆盖率结果就输出在 coverage/ 目录下。


在浏览器打开 coverage/ 目录下的 index.html 文件会出现以下页面:

vue/cli4 单元测试与覆盖率体系搭建_第3张图片

单击页面上的 components 可以看到改目录下的详细 .vue 文件覆盖率情况:

vue/cli4 单元测试与覆盖率体系搭建_第4张图片

再次单击 AppEmpty.vue 可以看到文件文件内的覆盖情况:

vue/cli4 单元测试与覆盖率体系搭建_第5张图片

最后列出 package.json 文件,以做参考:

{
  "name": "vue-cli4-unit-mocha-chai-nyc",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "nyc vue-cli-service test:unit --reporter=mochawesome",
    "test:e2e": "vue-cli-service test:e2e",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.4.0",
    "@vue/cli-plugin-e2e-cypress": "^4.4.0",
    "@vue/cli-plugin-eslint": "^4.4.0",
    "@vue/cli-plugin-router": "^4.4.0",
    "@vue/cli-plugin-unit-mocha": "^4.4.0",
    "@vue/cli-plugin-vuex": "^4.4.0",
    "@vue/cli-service": "^4.4.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/test-utils": "^1.0.3",
    "babel-eslint": "^10.1.0",
    "babel-plugin-istanbul": "^6.0.0",
    "chai": "^4.1.2",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "istanbul-instrumenter-loader": "^3.0.1",
    "mochawesome": "^6.1.1",
    "node-sass": "^4.12.0",
    "nyc": "^15.0.1",
    "sass-loader": "^8.0.2",
    "vue-template-compiler": "^2.6.11"
  }
}

6、使用 karma 扫描测试覆盖率


扫描测试覆盖率的另一个方法是使用 karma。


官网:http://karma-runner.github.io/latest/index.html


在前面的 1-5 节部分,所有的内容都是在第 1 节建立的项目 vue-cli4-unit-mocha-chai-nyc 中依次完成的。在本节内容开始之前,需要按照第 1 节的内容,重新创建一个项目:vue-cli4-unit-mocha-chai-karma 。


首先,依旧是安装需要的依赖:

npm i --save-dev karma karma-mocha mocha karma-chai \
karma-webpack karma-chrome-launcher karma-sourcemap-loader karma-spec-reporter \
[email protected] [email protected] cross-env


在项目根目录创建 karma.conf.js 文件,其内容如下:

const webpackConfig = require('@vue/cli-service/webpack.config.js')

module.exports = function (config) {
  config.set({
    frameworks: ['mocha'],

    files: [
      'tests/**/*.spec.js'
    ],

    preprocessors: {
      '**/*.spec.js': ['webpack', 'sourcemap']
    },

    webpack: webpackConfig,

    reporters: ['spec', 'coverage'],
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    },

    autoWatch: true,

    browsers: ['ChromeHeadless']
  })
}


然后,修改 babel.config.js 文件:

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
+ env: {
+   test: {
+     plugins: ["istanbul"]
+ 	}
+ }
}


接着,修改 package.json 文件的 test:unit 命令为:

"test:unit": "cross-env NODE_ENV=test karma start --single-run",


关于这里使用 BABEL_ENV=test 还是 NODE_ENV=test,建议是 NODE_ENV=test

Karma can’t handle split chunks.

  • In @vue/cli-service v3, unless you explicitly set NODE_ENV to production, splitChunks won’t be turned on.
  • In @vue/cli-service v4, unless you explicitly set NODE_ENV to test, splitChunks will be turned on by default.

It is a good practice to always set NODE_ENV to test during unit testing because many of the packages in Node.js ecosystem follows this convention.

Actually, with NODE_ENV=test, you can skip BABEL_ENV=test because it will default to NODE_ENV.


最后,我们执行 test:unit 命令:

npm run test:unit                                    

> [email protected] test:unit /Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-karma
> cross-env NODE_ENV=test karma start --single-run



 DONE  Compiled successfully in 671ms

ℹ 「wdm」: Hash: f44d39181b90d9235a4b
Version: webpack 4.43.0
Time: 671ms
Built at: 2020-05-29 09:55:03
                                                                                                 Asset       Size                   Chunks             Chunk Names
../../../../../../Users/heyi/File/Front-End/demo/_unit/vue-cli4-unit-mocha-chai-karma/dist/favicon.ico   4.19 KiB                           [emitted]  
                                                                                                app.js   1.22 MiB                      app  [emitted]  app
                                                                                 img/logo.82b9c7a5.png   6.69 KiB                           [emitted]  
                                                                                            index.html  889 bytes                           [emitted]  
                                                                            tests/unit/example.spec.js    3.1 MiB  tests/unit/example.spec  [emitted]  tests/unit/example.spec
Entrypoint app = app.js
Entrypoint tests/unit/example.spec = tests/unit/example.spec.js
[0] multi ./src/main.js 28 bytes {app}
[./node_modules/@babel/runtime/helpers/interopRequireDefault.js] 147 bytes {app} {tests/unit/example.spec} [built]
[./node_modules/@vue/test-utils/dist/vue-test-utils.js] 400 KiB {tests/unit/example.spec} [built]
[./node_modules/chai/index.js] 40 bytes {tests/unit/example.spec} [built]
[./node_modules/chai/lib/chai.js] 1.23 KiB {tests/unit/example.spec} [built]
[./node_modules/vue-loader/lib/runtime/componentNormalizer.js] 2.71 KiB {app} {tests/unit/example.spec} [built]
[./node_modules/vue-template-compiler/index.js] 865 bytes {tests/unit/example.spec} [built]
[./node_modules/vue/dist/vue.runtime.esm.js] 222 KiB {app} {tests/unit/example.spec} [built]
[./src/App.vue] 566 bytes {app} [built]
[./src/components/HelloWorld.vue] 599 bytes {app} {tests/unit/example.spec} [built]
[./src/components/HelloWorld.vue?vue&type=script&lang=js&] 576 bytes {app} {tests/unit/example.spec}
[./src/components/HelloWorld.vue?vue&type=template&id=469af010&scoped=true&] 427 bytes {app} {tests/unit/example.spec}
[./src/main.js] 2.38 KiB {app} [built]
[./src/router/index.js] 3.23 KiB {app} [built]
[./tests/unit/example.spec.js] 3.27 KiB {tests/unit/example.spec} [built]
    + 160 hidden modules
Child html-webpack-plugin for "index.html":
         Asset     Size  Chunks  Chunk Names
    index.html  534 KiB       1  
    Entrypoint undefined = index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./public/index.html] 928 bytes {1} [built]
    [./node_modules/lodash/lodash.js] 528 KiB {1} [built]
    [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {1} [built]
ℹ 「wdm」: Compiled successfully.
29 05 2020 09:55:03.205:INFO [karma-server]: Karma v5.0.9 server started at http://0.0.0.0:9876/
29 05 2020 09:55:03.207:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
29 05 2020 09:55:03.231:INFO [launcher]: Starting browser ChromeHeadless
29 05 2020 09:55:03.790:INFO [Chrome Headless 83.0.4103.61 (Mac OS 10.14.6)]: Connected on socket WTpGcR9Fum14KL6rAAAA with id 16752070

  HelloWorld.vue
    ✓ renders props.msg when passed

Chrome Headless 83.0.4103.61 (Mac OS 10.14.6): Executed 1 of 1 SUCCESS (0.012 secs / 0.009 secs)
TOTAL: 1 SUCCESS


=============================== Coverage summary ===============================
Statements   : 100% ( 6/6 )
Branches     : 100% ( 0/0 )
Functions    : 100% ( 3/3 )
Lines        : 100% ( 6/6 )
================================================================================


执行成功后会在项目根目录下生成 coverage/** 文件夹,放着输出的测试覆盖率,我们在浏览器打开 coverage/lcov-report/index.html 文件,其页面如下:

vue/cli4 单元测试与覆盖率体系搭建_第6张图片

与使用 nyc 输出的页面一样,也可以单击列表的 src/components/ 查看该目录下的详细文件:





查看 HelloWorld.vue 文件详情:

vue/cli4 单元测试与覆盖率体系搭建_第7张图片



由于在写这篇记录的时候,发现存在依赖(karma-coverage、babel-plugin-istanbul)不能直接安装最新版本,下面列出完整的 package.json 文件,以做参考:

{
  "name": "vue-cli4-unit-mocha-chai-karma",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "test:unit": "cross-env NODE_ENV=test karma start --single-run",
    "test:e2e": "vue-cli-service test:e2e",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^4.4.0",
    "@vue/cli-plugin-e2e-cypress": "^4.4.0",
    "@vue/cli-plugin-eslint": "^4.4.0",
    "@vue/cli-plugin-router": "^4.4.0",
    "@vue/cli-plugin-unit-mocha": "^4.4.0",
    "@vue/cli-plugin-vuex": "^4.4.0",
    "@vue/cli-service": "^4.4.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "@vue/test-utils": "^1.0.3",
    "babel-eslint": "^10.1.0",
    "babel-plugin-istanbul": "^5.1.4",
    "chai": "^4.1.2",
    "cross-env": "^7.0.2",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "karma": "^5.0.9",
    "karma-chai": "^0.1.0",
    "karma-chrome-launcher": "^3.1.0",
    "karma-coverage": "^1.1.2",
    "karma-mocha": "^2.0.1",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-spec-reporter": "0.0.32",
    "karma-webpack": "^4.0.2",
    "mocha": "^7.2.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^8.0.2",
    "vue-template-compiler": "^2.6.11"
  }
}


使用 karma 其它常用依赖:

chai-spies karma-chai-spies \
sinon sinon-chai karma-sinon-chai

你可能感兴趣的:(vue,vue,单元测试)