浅析前端vue项目使用单元测试框架一

 

测试库:https://www.awesomes.cn/repos/Applications/Testings

/*************************************************************************************分割线************************************************************************************************************************************/

第一部分:配置操作流程

 

步骤一、通过 vue init webpack-simple test-xxxxx安装的简单工程项目,在webpack-template项目中,需要手动安装 "karma"、"karma-chrome-launcher"、"karma-coverage"、 "karma-mocha","karma-phantomjs-launcher"、"karma-phantomjs-shim","karma-sinon-chai"、"karma-sourcemap-loader"、"karma-spec-reporter"、"karma-webpack"、"mocha"、“chai”、"sinon"、"sinon-chai",可能需要安装“cross-env”

cnpm i xxx --save-dev 即可

 

步骤二、package.json中script脚本命令中添加"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",

 

步骤三、因为我们并不是用脚手架集成引入测试框架(chai/Mocha等),所以项目目录结构中不存在测试目录,需我们手动创建添加:

 

    1)与src、node_modules等文件夹同级结构中创建test目录

    2)test目录下创建unit文件夹

    3)unit目录下存在3个文件(.eslintrc、index.js、karma.conf.js)&1个文件夹(specs)

     

    .eslintrc中内容:

{
  "env": { 
    "mocha": true
  },
  "globals": { 
    "expect": true,
    "sinon": true
  }
}

 

index.js中内容:

import Vue from 'vue'

Vue.config.productionTip = false

// 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中内容:

// This is a karma config file. For more details see
//   http://karma-runner.github.io/0.13/config/configuration-file.html
// we are also using it with karma-webpack
//   https://github.com/webpack/karma-webpack

var webpackConfig = require('../../webpack.test.conf')

module.exports = function (config) {
  config.set({
    // to run in additional browsers:
    // 1. install corresponding karma launcher
    //    http://karma-runner.github.io/0.13/config/browsers.html
    // 2. add it to the `browsers` array below.
    //browsers: ['PhantomJS'],
    browsers: ['Chrome'],    
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
    reporters: ['spec', 'coverage'],
    files: ['./index.js'],
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    webpack: webpackConfig,
    webpackMiddleware: {
      noInfo: true
    },
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

 

/***********end**************/

specs文件夹中存放测试脚本文件,

例如,在src中component中有个HelloWorld.vue/或者untils下HelloFarmFriend.js文件;注,此时在specs目录下对应存在测试脚本文件,如HelloWorld.spec.js 或者HelloFarmFriend.spec.js

正如下文举例所描述那样。

 

步骤四、需要添加webpack.test.conf.js文件(与src同级),内容:

'use strict'
// This is the webpack config used for unit tests.

// const utils = require('./utils')
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.config')

const webpackConfig = merge(baseWebpackConfig, {
// use inline sourcemap for karma-sourcemap-loader
module: {
// rules: utils.styleLoaders()
},
devtool: '#inline-source-map',
resolveLoader: {
alias: {
// necessary to to make lang="scss" work in test when using vue-loader's ?inject option
// see discussion at https://github.com/vuejs/vue-loader/issues/724
'scss-loader': 'sass-loader'
}
},
plugins: [
new webpack.DefinePlugin({
'process.env': require('./config/test.env')
})
]
})

// no need for app entry during tests
delete webpackConfig.entry

module.exports = webpackConfig

 

此时,还需要cnpm i --save-dev webpack-merge,理由是webpack配置需要的

 

最后,添加config目录(与src同级)

1.dev.env.js中

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

 

2.prod.env.js

'use strict'
module.exports = {
NODE_ENV: '"production"'
}

 

3.test.env.js

'use strict'
const merge = require('webpack-merge')
const devEnv = require('./dev.env')

module.exports = merge(devEnv, {
NODE_ENV: '"testing"'
})

大功告成!试试

 

步骤五、完成上述步骤后,运行npm run unit命令即可。运行后会在test下unit目录中生成一个coverage目录,此文件为代码测试覆盖率结果,找到并打开index.html即可查看测试报告。

 

这里先以vue单文件组件为例子

src/components/HelloWorld.vue

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

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

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

此时测试脚本文件为:HelloWorld.spec.js

import Vue from 'vue'
import HelloWorld from '../../../src/components/HelloWorld'

describe('Hello大橙子', () => {
  it('should render correct contents', () => {
    const Constructor = Vue.extend(HelloWorld)
    const vm = new Constructor().$mount()
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('dachengz Vue.js App')
  })
})

上述自己创建对应文件,保存

 

/****占位utils.js测试***/

这里先以需要测试js文件为例子

1.创建一个utils目录,添加一个add.js(demo)

function add(x,y){
return x + y
}

module.exports = add

2.同样操作,在test/unit/specs目录中添加add.spec.js文件

// add.test.js
import add from '../../../utils/add'

describe('测试加法',() => {
it('1加1 应该等于2', () => {
expect(add(1,1)).to.be.equal(2);
})
})

上述就是针对纯待测js文件进行单元测试,保存代码,运行npm run unit即可,结果如下图:

 

/*********************************************************************************分割线*********************************************************************************************************************/

第二部分:针对测试框架说明介绍(偏概念)

应用组件功能测试

组件测试vue-TDD的开发流程 (TDD什么意思?下面有介绍)
{
   1.编写组件测试-在单元测试中将设计组件的名称、属性接口、事件接口、用断言工具确定衡量这个组件正确的标准
   2.编写组件代码-以单元测试为引导程序,编写组件真实的实现代码,让测试通过
   3.运行测试,并看到测试通过
   4.重构
}

测试工具:

测试管理工具(测试加载器) Karma

{
    Karma-自动化测试程序的入口,执行以下任务 
    1.为测试程序注入指定依赖包
    2.可同时在一个或多个浏览器宿主中执行测试,满足兼容性测试需求
    3.执行代码覆盖性测试
    4.输出测试报告
    5.执行自动化测试
}

测试框架 Mocha (测试Node和浏览器js代码)
vue官网单元测试框架 Vue Test Utils

断言库 chai / sinon

{
    expect和should是BDD风格,使用相同链式语言组织断言,不同点在于初始化断言方式:
        1.expect使用构造函数来创建断言对象实例
2.should通过为Object.prototype新增方法来实现断言(should不支持IE); expect直接指向chai.expect
而should则是chai.should()

    
    注:在构建vue-cli工程时,添加了单元测试是不需要手动配置chai;
    chai和sinon 被Karma通过Karma-sinon-chai插件直接嵌入到单元测试的上下文中,不用import就可以使用
}

那问题来了?sinon是什么?辅助库 sinon-chai
{
    sinon: 负责仿真
    三个方法: 调用侦测Spy / 接口仿真 Stub / 对象仿真 Mock
}


测试浏览器 chrome (PhantomJs 无界面浏览器,速度比chrome快)

测试覆盖率统计工具 Karma-coverage


test文件夹下unit,单元测试相关文件


specs-中存放测试脚本(所有的测试文件, 都将放specs这个目录下, 并以测试脚本名.spec.js结尾命名)
coverage文件里存放测试报告,html打开的是代码覆盖率
Karma.config.js配置文件


npm run unit 输出

demo:

1.被测试的组件HelloWorld.vue

<template>
  <div class="hello">
    <h1>Welcome to Your Vue.js App</h1>
  </div>
</template>


2.测试脚本HelloWorld.spec.js

import HelloWorld from '@/components/HelloWorld';
import { mount, createLocalVue, shallowMount } from '@vue/test-utils'

describe('HelloWorld.vue', () => {
  it('should render correct contents', () => {
    const wrapper = shallowMount(HelloWorld);
    let content = wrapper.vm.$el.querySelector('.hello h1').textContent;
    expect(content).to.equal('Welcome to Your Vue.js App');
  });
});

describe是"测试套件",表示一组相关的测试。它是一个函数,
第一个参数是测试套件的名称("加法函数的测试"),
第二个参数是一个实际执行的函数。

it是"测试用例"(test case),表示一个单独的测试,是测试的最小单位。
它也是一个函数,第一个参数是测试用例的名称,第二个参数是一个实际执行的函数。
所有的测试用例(it块)都应该含有一句或多句的断言

断言(判断源码的实际执行结果与预期结果是否一致),如果不一致, 就会抛出错误
expect(content).to.equal('Welcome to Your Vue.js App')实际为变量content应等于'Welcome to Your Vue.js App'
Vue的脚手架提供的断言库是sino-chai, 是一个基于Chai的断言库

chai官方文档:https://www.chaijs.com/
chai官方文档翻译:https://www.jianshu.com/p/f200a75a15d2


注意:Mocha测试钩子

在describe块中提供了四个钩子: before(), after(), beforeEach(), afterEach(). 它们会在以下时间执行:

describe('钩子说明', function() { // 第一个参数提示要测试什么;第二个匿名函数

  before(function() {
    // 在所有场景执行之前执行(执行一次)
  });

  after(function() {
    // 在所有场景执行之后执行(执行一次)
  });

  beforeEach(function() {
    // 在每个场景测试执行之前执行
  });

  afterEach(function() {
    // 在每个场景执行完成之后执行
  });

});

举个例子:
describe('Array', () => {
  let expectTarget = []

  beforeEach(() => {
    // 在每个场景测试执行之前执行
    expectTarget.push(1)
  });

  afterEach(() => {
    // 在每个场景执行完成之后执行
    expectTarget = []  // 清空
  });

  it('应该存在一个为1的整数', () => {
    expect(expectTarget[0]).to.eqls(1)
  });

  it('应该存在多个的期望值检测', () => {
    expect(expectTarget[0]).to.eqls(1)
    expect(true).to.eqls(true)
  }); 
});



Mocha 官方文档:https://mochajs.org/
Mocha 官方文档翻译:https://www.jianshu.com/p/9c78548caffa
阮一峰 - 测试框架 Mocha 实例教程:http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html



vue-test-utils官方文档:https://vue-test-utils.vuejs.org/zh/api/#render
vue-test-utils常用api

find(): 返回匹配选择器的第一个DOM节点或Vue组件的wrapper, 可以使用任何有效的选择器
text(): 返回wrapper的文本内容
html(): 返回wrapper DOM的HTML字符串
trigger(): 在该 wrapper DOM 节点上触发一个事件
setData(): 设置data的属性并强制更新
it('find()/text()/html()方法', () => {
    const wrapper = mount(Counter);
    const h3 = wrapper.find('h3');
    expect(h3.text()).to.equal('Counter.vue');
    expect(h3.html()).to.equal('<h3>Counter.vue</h3>');
  })

it('trigger()方法', () => {
    const wrapper = mount(Counter);
    const buttonOfSync = wrapper.find('.sync-button');
    buttonOfSync.trigger('click');
    buttonOfSync.trigger('click');
    const count = Number(wrapper.find('.num').text());
    expect(count).to.equal(2);
  })

 it('setData()方法',() => {
    const wrapper = mount(Counter);
    wrapper.setData({foo: 'bar'});
    expect(wrapper.vm.foo).to.equal('bar');
  })


遇到vue-异步测试情况

Mocha异步测试在it内加一个done函数,在所有的断言执行完成后调用done()就可以释放测试用例并告知Mocha测试的执行结果

例子:一个User对象,具有一个save方法,这个方法通过ajax将数据保存到服务器端,若服务端没有返回错误,即认为这个save方法是成功的

describe('User', () => {
  describe('#save()方法', () => {
    it('应该成功保存到服务器端且不会返回任何错误信息', done => {
      const user = new User('纳尼')
      user.save( err => {
        if (err) done(err) // 如果返回错误码直接将错误码输出到控制台
else done()
      })
    })
  })
})

优化,将done作为回调参数使用
describe('User', () => {
  describe('#save()方法', () => {
    it('应该成功保存到服务器端且不会返回任何错误信息', done => {
      const user = new User('纳尼')
      user.save(done)
    })
  })
})

使用promise的另一种替代方案就是将chai断言作为it的返回值,将chai断言作为一个promise对象返回
让Mocha进行链式处理,需要借助chai-as-promised库支持(https:www.npmjs.com/package/chai-as-promised) 
在Mocha v3.0.0之后的版本中,如果直接构造ES6上的Promise对象则会被Mocha认为是非法的

it('应该完成此测试', done => {
    return new Promise( resolve => {
      assert.ok(true)
      resolve()
    }).then(done)
})
会提示异常信息



扩展:基于Nightwatch的端到端测试环境

端到端测试,简称e2e (End to End test):

    ***侧重于检测界面的交互效果与操作逻辑是否正确
{
    1.单元测试侧重:检验函数的输出结果
    2.e2e侧重:从用户视角,对真实系统的访问行为进行仿真
}

简单理解:

    1.单元测试的功能只能确保单个组件的质量,无法测试具体的业务流程是否运作正常
    2.e2e是面对 组件与组件之间、用户与真实环境之间的一种集成性测试

demo: https://github.com/Lee-Tanghui/Vue-Testing-Demo/issues
组件单元测试:https://blog.csdn.net/hsany330/article/details/73650020
***更多请参考官网例子:https://cn.vuejs.org/v2/guide/unit-testing.html


补充下:具体去google一下
Test-Driven Development(TDD)即测试驱动开发,一种测试先于编写代码的思想用于指导软件开发
Behavior Driven Development(BDD)行为驱动开发是一种敏捷软件开发的技术


单元测试(白盒测试),主要用于测试开发人员编写的代码是否正确,这部分工作都是开发人员自己来做的;通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为

BDD(灰盒测试、黑盒测试)


posted @ 2018-10-12 10:45  加勒比大橙子  阅读(8251)  评论(0编辑  收藏  举报