专栏名称: 尤水就下
前端开发
目录
相关文章推荐
成方三十二  ·  【总台央视】激情亚冬 逐梦冰雪 ... ·  20 小时前  
金融早实习  ·  米哈游(mihoyo)招聘战略投资岗 ·  昨天  
国际金融报  ·  三日涨超7%!DeepSeeK概念“点燃”恒生科技 ·  昨天  
金融早实习  ·  TCL财务2025届校园招聘 ·  2 天前  
香帅的金融江湖  ·  全球资产动向速递 -2月第2周 | ... ·  3 天前  
51好读  ›  专栏  ›  尤水就下

所谓单元测试

尤水就下  · 掘金  ·  · 2019-07-30 09:03

正文

阅读 151

所谓单元测试

前言

对于大部分前端同学们来说,可能平时都没怎么接触过单元测试🙌,顶多在初始化 Vue 项目的时候看到过它问你要不要测试,或者听说过 karma、mocha 这些名词,但具体就不得而知了。其实这东西并不复杂,只是我们没去学而已,它就像 Vue 一样容易上手,多写个几天,就能够像写 Vue 一样如鱼得水了🐠。

为什么要单元测试

所以,我们为什么需要单元测试呢🤔?
原因很简单:就是为了减少 bug、提高产品稳定性,而不是为了测试而测试。对我们开发来说,它的好处也是显而易见的:就是保证代码质量。想想我们平时代码出问题的时候,是不是常常不敢去删除原有的代码,而是像打补丁一样往上加代码,主要原因就是没有测试保障,你也不知道自己改了对不对、影响大不大💣。所以,如果有时间的话,单元测试还是可以写写的。

什么是单元测试

那么,什么是单元测试呢?简单来说就是对(一些不常变动的)单元进行测试,对前端来说你可以强行理解为😁:就是对一些通用函数和通用组件进行测试。再直白点说就是写一些测试代码来验证你的源代码是否符合预期,仅此而已。
正经点说,测试又可分为测试驱动开发(TDD)和行为驱动开发(BDD)两种,什么意思呢🤔?

  • TDD:通过测试来推动整个开发的进行(就是测着测着出了 bug 就返回修改代码,注重测试结果)
  • BDD:通过行为来推动整个开发的进行(就是按着按着出了 bug 就返回修改代码,注重测试逻辑)

其实这些概念并不重要,我们只要了解就行,毕竟这些概念也是近几年才出现,只是个称呼。
那既然单元测试是个不错的东西,为什么大部分人都不写呢😅?说到这里,不得不说下单元测试最大的一个缺点:就是在一开始需要花很多时间。但是在大部分情况下,我们不是在写需求就是在写需求的路上,没时间搞它,所以就不懂。与之矛盾的是它的优点:就是以后可以花更少的时间😯,尤其是如果你在开发新特性时,它能大大减少副作用。
ps: 单元测试的原则就是要尽量独立和单一,这样才有利于测试、维护和理解。当然即使用例全部通过了也要经过人工测试,因为我们不能保证集成在一起就不会有问题😬。

前置知识(关于测试工具)

这里先抛给大家一幅测试工具的关系图:

没看懂也没关系,下面会讲解一波,但你要记住 karma 包含其他三个!!!

karma

karma 不是一个测试框架,也不是一个断言库,而是一个测试集成工具,它的主要作用就是集成其他各种测试工具(支持按需配置,你可以通过 karma 的配置文件来集成你喜欢的框架、断言库和浏览器等),然后自动打开浏览器运行你的测试脚本,测试结果通常会显示在命令行中。此外它还可以监听测试文件的变化,然后自执行。

  • 总结:你可以粗浅的认为 karma 就是用来打开浏览器的。

mocha

mocha 是一个很常用的测试框架(类似的有 jasmine 和 jest 等),它既可以在 Node 中运行,也可以在浏览器中运行。它的主要作用是提供一些方便的语法来编写测试用例,以及对用例进行分组等。一个测试脚本可以由多个 descibe 组成,每个 describe 又可以由多个 it 组成。descibe 主要就是用来分组,it 就是具体的测试用例代码。这里简要看下它的语法,如下:

describe('分组一', () => {
    it('测试用例描述一', () => {})
    it('测试用例描述二', () => {})
})
describe('分组二', () => {
    it('测试用例描述一', () => {})
    it('测试用例描述二', () => {})
})
复制代码

这个就是固定写法,记住就行,没有什么为什么👀。

  • 总结:你可以粗浅的认为 mocha 就是用来编写测试用例的。

chai

因为 mocha 本身是不带断言的,所以需要和断言库结合使用。这里我们选择 chai 这个断言库。它有三种不同风格的写法,但意思是一样的,就像下面这样:

这里我们采用的是中间 expect 的写法,因为它比较符合自然语言(什么是自然语言?就是读起来比较顺)。然后举些例子🌰:

expect(1 + 1).to.be.equal(2); // 我期待 1 + 1 等于 2
expect('hello').to.be.a('string'); // 我期待 'hello' 是个字符串
expect('').to.be.empty; // 我期待 '' 是个空值
expect({ a: 1 }).to.have.property('a'); // 我期待 { a: 1 } 有一个属性 a
复制代码

要注意的是 chai 断言库中,to be been is has have 等这些词是没有意义的,只是为了读起来比较顺而已,事实上读起来也确实顺,如果你懂点基础英语的话。

  • 总结:chai 是一个语义化的断言库

sinon

sinon 是一个测试辅助工具,它的本质工作是测试替身,也就是用来替换测试中的部分代码,使测试代码变得简洁。比如我们要测一个函数是否被调用过,就可以借助 sinon.fake() 来实现,这是一个特殊的函数,现在不懂没关系,用的时候你就知道了。

  • 总结:sinon 是一个测试辅助工具

以上就是单元测试所需用到的大部分工具知识,如果大家想要加深了解的话,可以自行百度。

开始实践

虽然花了这么大篇幅扯了这么久🌚,但上面的背景知识对我们的理解是很有帮助的。不过,好记性不如写代码,下面就让我们赶紧撸起来吧💪。

初始化项目

先用 vue-cli 快速生成一个最简版的 Vue 项目,这里我们选择 default。

安装各种依赖

要安装的依赖有点多,我就不详细说每个东西是干嘛的了,装就是了。

yarn add karma karma-chai karma-chai-spies karma-chrome-launcher karma-mocha karma-sinon-chai mocha chai sinon sinon-chai karma-webpack vue-loader -D
复制代码

新建 karma.conf.js 配置文件

执行 ./node_modules/karma/bin/karma init 命令,一路回车,就会在根目录生成一个 karma.conf.js 配置文件。 然后对这个文件做点修改,代码如下:

const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = function(config) {
    config.set({
        frameworks: ['mocha', 'sinon-chai', 'chai'], // 这是配置依赖包,karma 会自动引入这些包,后续我们就不需要 import 了
        files: [
            'test/**/*.test.js', // 这是要执行的测试代码
        ],
        preprocessors: { // 这是在测试之前要先用 webpack 处理一下
            "src/**/*.*": ["webpack"],
            "test/**/*.test.js": ["webpack"]
        },
        webpack: {
            mode: 'development',
            module: {
                rules: [{
                    test: /\.js$/,
                    exclude: /(node_modules)/,
                    use: [{ loader: 'babel-loader'}]
                },
                {
                    test: /\.vue$/,
                    loader: 'vue-loader'
                }]
            },
            plugins: [
                new VueLoaderPlugin()
            ]
        }
    })
})
复制代码

顺便在根目录下新建一个空的 test 目录。
再顺便在 package.json 里面加上一个脚本命令 "test": "karma start --single-run"
最后的目录结构大致如下:

对函数进行测试

ok,接下来让我们热个身,写个函数的测试用例。

写一个简单的函数

在 src 目录下新建一个 utils.js 文件,其内容如下:

// utils.js
function add(a, b) {
    return a + b
}
function multiply(a, b) {
    return a * b
}
export {
    add,
    multiply
}
复制代码

编写函数的测试用例

一般来说测试文件名和源码文件名是一致的,所以我们在 test 目录下新建一个 utils.test.js 文件。

import { add, multiply } from '../src/utils'

describe('工具函数测试', function() {
    it('求和函数测试', function() {
        let res = add(1, 1)
        expect(res).to.be.equal(2)
    })
    it('乘法函数测试', function() {
        let res = multiply(1, 1)
        expect(res).to.be.equal(1)
    })
})
复制代码

嗯,就这样,函数用例就编写完了,当然你也可以写的再复杂点。

运行函数的测试用例

我们直接运行 yarn test 就能够看到如下结果:

可以看到我们的两个用例都通过了,也许你会问道我怎么知道它有没有运行呢,很简单,你可以把 equal 里面的值故意改成错的运行一下,形如这样: expect(res).to.be.equal(100) ,你将会得到如下结果:
可以看到它会给你明显的错误提示。当然你也可以在 expect 后面打个 log 证明它执行了,以上就是函数的测试方法,是不是 easy 啊✌。
ps: 我们执行 yarn test 就是执行 karma start --single-run ,karma 会根据 karma.conf.js 的配置内容来执行 test 目录下的代码,并自动打开浏览器测试,结束后又自动关闭浏览器(--single-run 的作用),如果有报错就会打印在控制台中。

对组件进行测试

接下来我们来看看 vue 组件是怎么测试的吧。首先,当然需要一个组件啦。

写一个简单的组件

在 src 下面新建一个简单的 demo.vue 组件,就像下面这样:

<!-- demo.vue -->
<template>
    <div class="demo" :class="isError ? 'demo--error' : ''" @click="$emit('click')">
        <span class="text" :style="`opacity: ${opacity}`" :data-msg="msg">哈哈哈</span>
        <slot></slot>
    </div>
</template>
<script>
export default {
    name: 'Demo',
    props: {
        msg: {
            type: String,
            default: ''
        },
        isError: {
            type: Boolean,
            default: false
        },
        opacity: {
            type: [String, Number],
            default: 1
        }
    }
}
</script>
复制代码

编写组件的基础测试用例

在 test 目录下新建 demo.test.js 文件,内容如下:

import Vue from 'vue/dist/vue.common.js'
import Demo from '../src/demo.vue'

Vue.config.productionTip = false
Vue.config.devtools = false

describe('Demo 组件测试', () => {
    it('存在', () => { // 首先得确保有 demo 这个东西
        expect(Demo).to.exist // 不是 undefined、null、0、''等 fasly 值就是 exist
    })
    describe('Demo 组件的基础功能测试', () => {
        it('.text 的文本内容测试', () => {
            const Constructor = Vue.extend(Demo)
            const vm = new Constructor().$mount() // 实例化组件
            console.log(vm.$el)
            expect(vm.$el.querySelector('.text').textContent).to.equal('哈哈哈') // 我期待 .text 元素的文本内容为 '哈哈哈'
        })
    })
})
复制代码

代码应该还算通俗易懂,其实测试用例的思路大体是一致的,主要核心思想就是:先实例化组件,然后用选择相应元素的一些可参照的东西进行断言,看看是否和预期相匹配。
ok,让我们运行 yarn test 看下效果:

显然这个用例也是 ok 的。那如何知道错了呢,同之前的函数一样,也故意把 equal('哈哈哈') 改错就行,之后就不再赘述了,就像下面这样:

编写组件的 props 测试用例

我们直接上代码,大家应该都能读懂,写法是一样样的😎:

// ...
describe('Demo 组件测试', () => {
    describe('Demo 组件的基础功能测试', () => {})
    describe('Demo 组件的 props 测试', () => {
        it('.text 的属性值为黄小芮', () => { // 测试标签属性
            const Constructor = Vue.extend(Demo)
            const vm = new Constructor({
                propsData: { // 这是传参的固定写法,不必纠结
                    msg: '黄小芮'
                }
            }).$mount()
            expect(vm.$el.querySelector('.text').getAttribute('data-msg')).to.equal('黄小芮') // 我期待 .text 元素的 data-msg 属性值为 '黄小芮'
        })
        
        it('.demo 是否有 demo--error 的样式名', () => { // 测试样式名
            const Constructor = Vue.extend(Demo)
            const vm = new Constructor({
                propsData: {
                    isError: true
                }
            }).$mount()
            expect(vm.$el.classList.contains('demo--error')).to.equal(true) // 我期待 vm.$el 的样式列表包含 demo--error 样式名
        })
        
        it('.text 的 opacity 样式', () => { // 测试 css 样式(放到页面中才会有样式)
            const div = document.createElement('div')
            document.body.appendChild(div)
            const Constructor = Vue.extend(Demo)
            const vm = new Constructor({
                propsData: {
                    opacity: 0.5
                }
            }).$mount(div)
            const ele = vm.$el.querySelector('.text')
            expect(getComputedStyle(ele).opacity).to.equal('0.5') // 我期待 .text 元素的 css 样式 opacity 值为 '0.5',注意这里是字符串,css 的属性值都是字符串
        })
    })
})
复制代码

编写组件的 slot 测试用例

这里也直接上代码,要注意的是 slot 和上面实例化组件的方法有点不太一样:

// ...






请到「今天看啥」查看全文


推荐文章
金融早实习  ·  米哈游(mihoyo)招聘战略投资岗
昨天
金融早实习  ·  TCL财务2025届校园招聘
2 天前
经典短篇阅读小组  ·  最大的悲剧不是坏人的嚣张
8 年前
人人都是产品经理  ·  深度解读|大众点评用户成长体系
7 年前
慧聪物联资讯  ·  IOT时代 信息安全建设刻不容缓
7 年前