M
M
文章目录
  1. i. Karma 框架
  2. ii. Mocha 框架
  • 实战
  • 优化
  • 参考
  • 学习单元测试

    对于前端测试,它是前端工程方面的一个重要分支,因此,在我们的组件库中怎么能少掉这么重要的一角呢?对于单元测试,主要分为两种

    • TDD(Test-Driven Development):测试驱动开发,注重输出结果。
    • BDD(Behavior Driven Development):行为驱动开发,注重测试逻辑。

    基于项目初始化自带的 Karma + Mocha 这两大框架对我们的组件库中的组件进行单元测试。

    对于 Karma + Mocha 这两大框架,相信大多数接触过单元测试的人都不会陌生,但这里我觉得还是有必要单独开一小节对着两大框架进行一个简单的介绍。

    i. Karma 框架

    • Karma 是一个基于 Node.js 的 JavaScript 测试执行过程管理工具(Test Runner)
    • Karma 是一个测试工具,能让你的代码在浏览器环境下测试
    • Karma 能让你的代码自动在多个浏览器,比如 chrome,firefox,ie 等环境下运行
    • 为了能让我们的组件库中的组件能够运行在各大主流 Web 浏览器中进行测试,我们选择了 Karma 。最重要的是 Karma 是 vue-cli 推荐的单元测试框架。如果你想了解更多有关 Karma 的介绍,请自行查阅 Karma 官网

    ii. Mocha 框架

    • Mocha 是一个 simple,flexible,fun 的测试框架
    • Mocha 支持异步的测似用例,如 Promise
    • Mocha 支持代码覆盖率 coverage 测试报告
    • Mocha 允许你使用任何你想使用的断言库,比如 chai 、should.js (BDD风格)、expect.js 等等
    • Mocha 提供了 before(), after(), beforeEach(), 以及 afterEach() 四个钩子函数,方便我们在不同阶段设置不同的操作以更好的完成我们的测试

    这里我介绍一下 mocha 的三种基本用法,以及 describe 的四个钩子函数(生命周期)

    1.describe(moduleName, function): describe 是可嵌套的,描述测试用例是否正确

    describe('测试模块的描述', () => {
    // ....
    });

    2.it(info, function):一个 it 对应一个单元测试用例

    it('单元测试用例的描述', () => {
    // ....
    })

    3.断言库的用法

    expect(1 + 1).to.be.equal(2)

    4.describe 的生命周期

    describe('Test Hooks', function() {

    before(function() {
    // 在本区块的所有测试用例之前执行
    });

    after(function() {
    // 在本区块的所有测试用例之后执行
    });

    beforeEach(function() {
    // 在本区块的每个测试用例之前执行
    });

    afterEach(function() {
    // 在本区块的每个测试用例之后执行
    });

    // test cases
    });

    实战

    在单元测试实战开始前,我们先看看 Karma 的配置,这里我们直接看 vue-cli 脚手架初始化出来的 karma.conf.js 文件里面的配置(具体用处我做了注释)

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

    module.exports = function karmaConfig (config) {
    config.set({
    // 浏览器
    browsers: ['PhantomJS'],
    // 测试框架
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
    // 测试报告
    reporters: ['spec', 'coverage'],
    // 测试入口文件
    files: ['./index.js'],
    // 预处理器 karma-webpack
    preprocessors: {
    './index.js': ['webpack', 'sourcemap']
    },
    // webpack配置
    webpack: webpackConfig,
    // webpack中间件
    webpackMiddleware: {
    noInfo: true
    },
    // 测试覆盖率报告
    coverageReporter: {
    dir: './coverage',
    reporters: [
    { type: 'lcov', subdir: '.' },
    { type: 'text-summary' }
    ]
    }
    })
    }

    接下来,我们再来对我们自己的 hello 组件进行简单的测试(只写一个测试用例),在 test/unit/specs 新建 hello.spec.js 文件,并写入以下代码

    import Vue from 'vue' // 导入Vue用于生成Vue实例
    import Hello from 'packages/hello' // 导入组件
    // 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
    describe('Hello.vue', () => {
    // 每个describe块应该包括一个或多个it块,称为测试用例(test case)
    it('render default classList in hello', () => {
    const Constructor = Vue.extend(Hello) // 获得Hello组件实例
    const vm = new Constructor().$mount() // 将组件挂在到DOM上
    // 断言:DOM中包含class为v-hello的元素
    expect(vm.$el.classList.contains('v-hello')).to.be.true
    const message = vm.$el.querySelector('.v-hello__message')
    // 断言:DOM中包含class为v-hello__message的元素
    expect(message.classList.contains('v-hello__message')).to.be.true
    })
    })

    测试实例写完,接下来就是进行测试了。执行 npm run test,走你 ~ ,输出结果

    hello.vue
    ✓ render default classList in hello

    优化

    从上面 hello 组件的测试实例可以看出,我们需要将组件实例化为一个Vue实例,有时还需要挂载到 DOM 上

    const Constructor = Vue.extend(Hello)
    const vm = new Constructor({
    propsData: {
    message: 'component'
    }
    }).$mount()

    如果之后每个组件拥有多个单元测试实例,那这种写法会导致我们最后的测试比较臃肿,这里我们可以参考 element 封装好的 单元测试工具 util.js 。我们需要封装 Vue 在单元测试中常用的一些方法,下面我将列出工具里面提供的一些方法

    /**
    * 回收 vm,一般在每个测试脚本测试完成后执行回收vm。
    * @param {Object} vm
    */
    exports.destroyVM = function (vm) {}

    /**
    * 创建一个 Vue 的实例对象
    * @param {Object|String} Compo - 组件配置,可直接传 template
    * @param {Boolean=false} mounted - 是否添加到 DOM 上
    * @return {Object} vm
    */
    exports.createVue = function (Compo, mounted = false) {}

    /**
    * 创建一个测试组件实例
    * @param {Object} Compo - 组件对象
    * @param {Object} propsData - props 数据
    * @param {Boolean=false} mounted - 是否添加到 DOM 上
    * @return {Object} vm
    */
    exports.createTest = function (Compo, propsData = {}, mounted = false) {}

    /**
    * 触发一个事件
    * 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
    * mouseenter, mouseleave, mouseover, keyup, change, click 等
    * @param {Element} elm - 元素
    * @param {String} name - 事件名称
    * @param {*} opts - 配置项
    */
    exports.triggerEvent = function (elm, name, ...opts) {}

    /**
    * 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
    * @param {Element} elm - 元素
    * @param {*} opts - 配置选项
    */
    exports.triggerClick = function (elm, ...opts) {}

    下面我们将使用定义好的测试工具方法,改造 hello 组件的测试实例,将 hello.spec.js 文件进行改造

    import { destroyVM, createTest } from '../util'
    import Hello from 'packages/hello'

    describe('hello.vue', () => {
    let vm
    // 测试用例执行之后销毁实例
    afterEach(() => {
    destroyVM(vm)
    })
    it('render default classList in hello', () => {
    vm = createTest(Hello)
    expect(vm.$el.classList.contains('v-hello')).to.be.true
    const message = vm.$el.querySelector('.v-hello__message')
    expect(message.classList.contains('v-hello__message')).to.be.true
    })
    })

    重新执行 npm run test,输出结果

    hello.vue
    ✓ render default classList in hello

    参考

    支持一下
    扫一扫,支持一下