吃透 Vue 项目开发实践|16个方面深刻前端工程化开发技巧《中》

前言

据上节文章发布已经有了两个星期了。期间收到了 1000+ 个赞,30000+ 阅读量,这是我万万没想到的。本身的文章能有这么高的关注度,真的很使人满意!javascript

可是相反,写文章的压力更加大了。一篇文章老是反反复复的修改,老是担忧本身的认知水平技术水平不够,甚至致使有些地方会误导读者。css

也会揣摩本身写做风格有没有什么问题、会不会太啰嗦、哪些地方没讲清楚等等...若是有很差的地方能够评论指出来,接受批评,批评也是一种进步的动力!html

原本准备上下两节写彻底部内容,发现实际不太可能,还没写完 4 章,就已经 6000—+ 字了。最后一节写完以后就准备回家过年了,这里提早祝你们新年快乐!前端

常规操做,先点赞后观看哦!你的点赞是我创做的动力之一!vue

全系列概览

问题

这节我将从 5 个方面来论述 vue 开发过程当中的一些技巧和原理。若是你还未观看上节文章,能够移步至16个方面深刻前端工程化开发技巧《上》观看。java

本节内容主要围绕下列问题展开:ios

  • 如何编写原生组件,以及组件编写的思考与原则?
  • 组件怎么通讯?有哪些方式?
  • 如何使用vuex 以及它的应用场景和原理
  • 如何使用过滤器,编写本身的过滤器
  • 如何使用 Jest 测试你的代码?TDD 与 BDD 的比较

实践

实践以前:我但愿你有以下准备,或者知识储备。git

  • 了解 npm/yarn/git/sass/pug/vue/vuex/vue-router/axios/mock/ssr/jest 的使用和原理。
  • 固然上面知识不了解也不要紧哈哈哈,文章中会提到大体用法和做用。

如何编写原生组件

组件编写原理

vue 编写组件有两种方式,一种是单文件组件,另一种函数组件。根据组件引入和建立还能够分为动态组件异步组件github

动态组件keep-alive使之缓存。异步组件原理和异步路由同样,使用import()实现异步加载也就是按需加载。vue-router

所谓 vue 单文件组件,就是咱们最多见的这种形式:

<template lang="pug">
	.demo
  	h1 hello
</template>
<script> export default { name: 'demo', data() { return {} } } </script>
<style lang="scss" scoped> .demo { h1 { color: #f00; } } </style>
复制代码

这里的template 也可使用 render 函数来编写

Vue.component('demo', {
  render: function (createElement) {
    return createElement(
      'h1',
	  'hello',
      // ...
    )
  }
})
复制代码

咱们能够发现render函数写模版让咱们更有编程的感受。对模版也能够编程,在vue 里面咱们能够很容易联想到,不少地方都有两种写法,一种是 template , 一种是js

好比:对于路由,咱们既可使用:to="",又可使用$router.push,这也许是 vue 用起来比较爽的缘由。

函数式组件是什么呢?

functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)

  • 单文件形式 2.5.0+
<template functional>
</template>
复制代码
  • Render 函数形式
Vue.component('my-component', {
  functional: true,
  render function (createElement, context) {
  	return createElement('div')
	}
}
复制代码

为何要使用函数组件呢?

最重要的缘由就是函数组件开销低,也就是对性能有好处,在不须要响应式和this的状况下,写成函数式组件算是一种优化方案。

组件写好了,须要将组件注册才能使用

组件注册的两种方式

组件注册分为两种,一种是全局注册,一种是局部注册

局部注册就是咱们经常使用的 Vue.component('s-button', { /* ... */ }),比较简单不详细论述

全局注册上节已经提到,在new Vue 以前在 mian.js 注册,这里还提到一种自动全局注册的方法 require.text

import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
  './components',
  // 是否查询其子目录
  false,
  /Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
  // 获取组件配置
  const componentConfig = requireComponent(fileName)
  const componentName = upperFirst(
    camelCase(
      // 获取和目录深度无关的文件名
      fileName
        .split('/')
        .pop()
        .replace(/\.\w+$/, '')
    )
  )
  // 全局注册组件
  Vue.component(
    componentName,
    componentConfig.default || componentConfig
  )
})
复制代码

基本原理和全局注册同样,就是将 components 中的组件文件名,appButton 变成 AppButton 做为注册的组件名。把原来须要手动复制的,变成之间使用 keys 方法批量注册。

实践开发一个 button 组件

如今,咱们以写一个简单的原生button组件为例,探讨一下组件开发的一些关键点。 写以前,咱们须要抓住 4 个核心的要点:

  • 用哪一个标签做为组件主体,button 仍是 div 标签
  • 如何根据属性控制 button 组件的颜色 color、形状 type、大小 size
  • 如何处理 button 组件的点击事件
  • 如何去扩展 button 组件的内容

这些思考点在其余原生组件开发和高阶组件封装里面也须要考虑到

首先看第一个问题,大部分原生组件第一考虑的地方,就是主要标签用原生<button></button>标签仍是用<div></div> 去模拟。

为何不考虑 <input/>呢,由于 <button> 元素比 <input> 元素更容易添加内部元素。你能够在元素内添加HTML内容(像<em><strong> 甚至 <img>),以及 ::after::before 伪元素来实现复杂的效果,而 <input> 只支持文本内容。

下面分析这两种写法的优劣

使用原生button标签的优点:

  1. 更好的标签语义化
  2. 原生标签自带的 buff,一些自带的键盘事件行为等

为何说更好的语义化呢?有人可能会说,可使用 role 来加强 div 的语义化。确实能够,可是可能存在问题——有些爬虫并不会根据 role 来肯定这个标签的含义。

另一点,对开发者来讲,<button></button><div role="button"></div>阅读起来更好。

使用 div 模拟的优点:

  1. 不须要关心button原生样式带来的一些干扰,少写一些覆盖原生 css 的代码,更干净纯粹。
  2. 所有用 div ,不须要再去找原生标签、深刻了解原生标签的一些兼容相关的诡异问题。
  3. 可塑性更强,也就是拓展性和兼容性更好。这也是大多数组件都会选择使用 div 做为组件主体的缘由。

貌似 div 除了语义不是很好之外,其余方面都还行,可是具体用哪种其实均可以,只要代码写的健壮适配性强,基本都没啥大问题。

咱们这里使用原生<button></button>做为主要标签,使用s-xx做为class前缀

为何须要使用前缀,由于在有些时候,好比使用第三方组件,多个组件之间的 class 可能会产生冲突,前缀用来充当组件 css 的一个命名空间,不一样组件之间不会干扰。

<template lang="pug">
   button.s-button(:class="xxx" :style="xxx" )
     slot
</template>
复制代码

而后,咱们看第二个问题:

如何根据属性来控制 button 的样式 其实这个很简单,基本原理就是:

  1. 使用 props 获取父组件传过来的属性。

  2. 根据相关属性,生成不一样的class,使用 :class="{xxx: true, xxx: 's-button--' + size}" 这种形式,在 style 里面对不一样的s-button--xxx 作出不一样的处理。

<template lang="pug">
  button.s-button(:class="" :style="" )
    slot
</template>
<script> export default { name: 's-button' data: return {} props: { theme: {}, size: {}, type: {} } } </script>
  
复制代码

如何使用事件以及如何扩展组件

扩展组件的原理,主要就是使用 props 控制组件属性,模版中使用 v-if/v-show 增长组件功,好比增长内部 ICON,使用:style``class控制组件样式。

还要注意的是咱们还要控制原生 type 类型,原生默认是 submit,这里咱们默认设置为 button

<template lang="pug">
 button.s-button(:class="" :style="" :type="nativeType")
   slot
   s-icon(v-if="icon && $slots.icon" name="loading")
</template>
<script> export default { name: 's-button' data: return {} props: { nativeType: { type: String, default: 'button' }, theme: {}, size: {}, type: {} } } </script>
复制代码

控制事件,直接使用 @click="" + emit

<template lang="pug">
  button.s-button(@click="handleClick")
</template>
<script> export default { methods: { handleClick (evt) { this.$emit('click', evt) } } } </script>
复制代码

最后总结下:

通常就直接使用template单文件编写组件,须要加强 js编写能力可使用render()

常规编写组件须要考虑:1. 使用什么标签 2. 如何控制各类属性的表现 3. 如何加强组件扩展性 4. 如何处理组件事件

对响应式this要求不高,使用函数functional组件优化性能。

基础组件一般全局注册,业务组件一般局部注册

使用keys()遍历文件来实现自动批量全局注册

使用import() 异步加载组件提高减小首次加载开销,使用keep-alive + is缓存组件减小二次加载开销

如何使用 vuex 以及它的应用

由来以及原理

咱们知道组件中通讯有如下几种方式:

1. 父组件经过 props 传递给子组件,不详细论述

2. 子组件经过 emit 事件传递数据给父组件,父组件经过 on 监听,也就是一个典型的订阅-发布模型

@v-on: 的简写

<template lang="pug">
<!--子组件-->
    div.component-1
<template>
export default {
  mounted() {
    this.$emit('eventName', params)
  }
}
</script>
复制代码
<!-- 父组件-->
<template lang="pug">
    Component-1(@eventName="handleEventName")
<template>
<script> export default { methods: { handleEventName (params) { console.log(params) } } } </script>
复制代码

3. 集中式通讯事件,主要用于简单的非父子组件通讯

原理很简单其实就是在 emiton 的基础上加了一个事件中转站 “bus”。我以为更像是现实生活中的集线器。

广泛的实现原理大概是这样的 “bus” 为 vue 的一个实例,实例里面能够调用emit,off,on 这些方法。

var eventHub = new Vue()

// 发布
eventHub.$emit('add', params)
// 订阅/响应
eventHub.$on('add', params)
// 销毁
eventHub.$off('add', params)
复制代码

可是稍微复杂点的状况,使用这种方式就太繁锁了。仍是使用 vuex 比较好。

从某种意义而言,我以为 vuex 不只仅是它的一种进化版。

  1. 使用store做为状态管理的仓库,而且引入了状态这个概念
  2. 它的模型彻底不同了,bus 模型感受像一个电话中转站

而 vuex 的模型更像是集中式的代码仓库。

git 相似,它不能直接修改代码,须要参与者提交 commit,提交完的 commit修改仓库,仓库更新,参与者 fetch 代码更新本身的代码。不一样的是代码仓库须要合并,而 vuex 是直接覆盖以前的状态。

管理 vuex

原理

“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有如下两点不一样

  • 响应式(改变属性后,触发组件中的状态改变,触发 dom
  • 不能直接改变状态(惟一途径是提交 mutation)

基本用法:就是在 state 里面定义各类属性,页面或组件组件中,直接使用 $store.state或者$store.getters来使用。若是想要改变状态state呢,就commit 一个mutation

可是假如我想提交一连串动做呢?能够定义一个action,而后使用 $store.dispatch 执行这个 action

使用action 不只能省略很多代码,并且关键是action 中可使用异步相关函数,还能够直接返回一个promise

而为何不直接到mutation中写异步呢? 由于mutation 必定是个同步,它是惟一能改变 state 的,一旦提交了 mutationmutation 必须给定一个明确结果。不然会阻塞状态的变化。

下面给出经常使用 vuex 的使用方式

新建 Store

新建一个store并将其余各个功能化分文件管理

import Vue from 'vue'
import Vuex from 'vuex'
import state from './states'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
    //在非生产环境下,使用严格模式
    strict: process.env.NODE_ENV !== 'production', 
    state,
    getters,
    mutations,
    actions,
    modules: {
        user
    }
})
复制代码

操做状态两种方式

  1. 获取状态
console.log(store.state.count)
复制代码
  1. 改变状态
store.commit('xxx')
复制代码

管理状态 states

单一状态树, 这也意味着,每一个应用将仅仅包含一个 store 实例。单一状态树让咱们可以直接地定位任一特定的状态片断,在调试的过程当中也能轻易地取得整个当前应用状态的快照。

// states 文件
export default {
    count: 0
}
复制代码

计算属性中返回,每当 state 中属性变化的时候, 其余组件都会从新求取计算属性,而且触发更新相关联的 DOM

const Counter = {
	template: '<div>{{count}}<div>',
	computed: {
		count() {
			return store.state.count
		}
	}
}
复制代码

管理取值器 getters

getters 至关于 store 的计算属性。不须要每次都要在计算属性中过滤一下,也是一种代码复用。 咱们在getters文件中管理

export default {
    count: (state) => Math.floor(state.count)
}
复制代码

管理 mutations

更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。Vuex 中的 mutation 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是咱们实际进行状态更改的地方

使用 types 大写用于调试,在mutationTypes 文件中export const ROUTE_ADD = 'ROUTE_ADD'

而后在mutations 文件中管理

import * as MutationTypes from './mutationTypes'
export default {
    [MutationTypes.ADDONE]: function(state) {
        state.count = state.count + 1
    },
    //...
}
复制代码
this.$store.commit(MutationTypes.ADDONE)
复制代码

管理 actions

mutations 相似,actions 对应的是dispatch,不一样的是action可使用异步函数,有种更高一级的封装。

// 简化
actions: {
  increment ({ commit }) {
    setTimeout(() => {
        commit(MutationTypes.ADDONE)
    }, 1000)
  }
}
// 触发
store.dispatch('increment')
复制代码

上述用法均可以使用载荷的形式,引入也可使用 mapXxxx 进行批量引入,这里不详细论述,有兴趣能够查看官网。

分模块管理状态

状态太多太杂,分模块管理是一个良好的代码组织方式。

import count from './modules/count'
export default new Vuex.Store({
    modules: {
        count
    }
})
复制代码

每个模块均可以有独立的相关属性

import * as ActionTypes from './../actionTypes'
export default {
    state: {
        count: 0
    },
    mutations: {
        ADD_ONE: function(state) { 
            state.count = state.count + 1
        }
    },
    actions: {
        [ActionTypes.INIT_INTENT_ORDER]: function({ commit }) {
            commit('ADD_ONE')
        }
    },
    getters: {
        pageBackToLoan: (state) => Math.floor(state.count)
    }
}
复制代码

应用场景

vuex 主要有几个应用场景,一个是用于组件通讯状态共享,一个是用于数据缓存,还有就是用于减小请求。这些场景归根节点都是对于缓存共享来讲的。

组件通讯

首先,状态统一管理在仓库,就实现了组件通讯的可能性。

当一个组件经过 commit 提交 mutation 就改了 state,其余组件就能够经过store.state获取最新的state,这样一来就至关于更新的值经过 store 传递给了其余组件,不只实现了一对一的通讯,还实现了一对多的通讯。

状态共享

咱们常用的一个场景就是权限管理

写权限管理时候,首次进入页面就要将权限所有拿到,而后须要分发给各个页面使用,来控制各个路由、按钮的权限。

/** * 判断用户有没有权限访问页面 */
function hasPermission(routeItem, menus) {
  return menus.some(menuItem => {
    return menuItem.name === routeItem.name
  })
}

/** * 递归过滤异步路由表,返回符合用户角色权限的路由表 * @param {*} routes 路由表 * @param {*} menus 菜单信息 */
function filterRoutes(routes, menus) {
  return routes.filter(routeItem => {
    if (routeItem.hidden) {
      return true
    } else if (hasPermission(routeItem, menus)) {
      const menuItem = menus.find(item => item.name === routeItem.name)
      if (menuItem && menuItem.children && menuItem.children.length > 0) {
        routeItem.children = filterRoutes(routeItem.children, menuItem.children)
        if (!routeItem.children.length) return false
      }
      return true
    } else {
      return false
    }
  })
}
const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: [],
    roles: [],
    user_name: '',
    avatar_url: '',
    onlyEditor: false,
    menus: null,
    personal: true,
    teamList: []
  },
  mutations: {}
}
export default permission
复制代码

并且权限还能够被更改,更改后的权限直接分发到其余页面组件中。这个场景要是不使用 vuex ,代码将会比较复杂。

数据缓存

store 是一个仓库,它从建立开始就一直存在,只有页面 Vuex.store 实例被销毁,state 才会被清空。具体表现就是刷新页面。

这个数据缓存适用于:页面加载后缓存数据,刷新页面请求数据的场景。在通常Hybrid中,通常不存在刷新页面这个按钮,因此使用 vuex 缓存数据能够应对大多数场景。

export default {
    state: {
        // 缓存修改手机号须要的信息
        changePhoneInfo: {
            nameUser: '',
            email: '',
            phone: ''
        },
    }
}
复制代码

若是须要持久化缓存,结合浏览器或 APP 缓存更佳。

export default {
    // 登录成功后,vuex 写入token,并写入app缓存,存储持久化
    [ActionTypes.LOGIN_SUCCESS]: function(store, token) {
        store.commit(MutationTypes.SET_TOKEN, token)
        setStorage('token', token)
        router.replace({ name: 'Home', params: { source: 'login' } })
    }
}
复制代码

减小请求(数据缓存的变种)

在写后台管理平台时候,常常会有 list 选型组件,里面数据从服务端拿的数据。若是咱们把这个 list 数据存储起来,下次再次使用,直接从 store 里面拿,这样咱们就不用再去请求数据了。至关于减小了一次请求。

如何使用过滤器,编写本身的过滤器

原理

假设我如今有个需求,须要将性别0、一、2,分别转换成男、女、不肯定这三个汉字展现。页面中多处地方须要使用。

<template lang="pug">
  .user-info
    .gender
      label(for="性别") 性别
      span {{gender}}
</template>
复制代码

完成这个需求,咱们知道有 4 种方式:

  1. 使用 computed 方法
  2. 使用 methods 方法
  3. 使用 utils 工具类
  4. 使用 filters

应该选择哪一种方式呢?

我从下面三个方面来论述这个问题

1. 可实现性

  • 使用 computed 实现成功,咱们知道computed不是一个函数是没法传参的,这里有个技巧,return 一个函数接受传过来的参数
// ...
  computed: {
    convertIdToName() {
      return function(value) {
        const item = list.find(function(item) {
          return item.id === value
        })
        return item.name
      }
    }
  }
复制代码

  • 使用 methods 实现成功,这里直接能够传参数,一种常规的写法。

注意 methodscomputeddata 相互以前是不能同名的

// ...
  methods: {
    convertIdToName(value) {
      const item = list.find(function(item) {
        return item.id === value
      })
      return item.name
    }
  }
复制代码
  • 使用 utilsmethods 差很少基本上也能够实现
  • 使用 filter 也是实现的,有个能够和methodscomputed同名哦
filters: {
    console.log(this.render)
    convertIdToName(value) {
      const item = list.find(function(item) {
        return item.id === value
      })
      return item.name
    }
  },
复制代码

总的来讲他们所有均可以实现这个需求

2. 局限性

  • computedmethodsdata 三者互不一样名,他们没办法被其余组件使用,除非经过 mixins
  • filtersutils 没法访问 this ,也就是于响应式绝缘。可是经过定义全局filters,能够其余地方使用,另外还能够直接加载第三方filterutils

3. 总结比较

filtersutils 归属一对,他们既是脱离了 this,得到了自由,又是被this 弃之门外。相反 methodscomputedthis 牢牢站在一块儿,但又是没法得到自由。

例子

编写一个简单的千分位过滤器

export const thousandBitSeparator = (value) => {
  return value && (value
    .toString().indexOf('.') !== -1 ? value.toString().replace(/(\d)(?=(\d{3})+\.)/g, function($0, $1) {
      return $1 + ',';
    }) : value.toString().replace(/(\d)(?=(\d{3})+$)/g, function($0, $1) {
      return $1 + ',';
    }));
}
复制代码

使用 vue-filter 插件

两款插件

vue-filter:www.npmjs.com/package/vue… 使用 use 引入

vue2-filters:www.npmjs.com/package/vue… 使用mixins 引入

有须要的话,我通常就用第二个了,大多数都是本身写一下小过滤器

自定义过滤器以后,直接全局自动注册,其余地方均可以使用

注册全局过滤器

遍历过滤属性值,一次性所有注册

for (const key in filters) {
    Vue.filter(key, filters[key])
}
复制代码

如何使用 Jest 测试你的代码

原理

咱们思考一下测试 js 代码须要哪些东西

  1. 浏览器运行环境
  2. 断言库

若是是测试 vue 代码呢? 那得再加一个 vue 测试容器

Jest + Vue

安装依赖

{
    "@vue/cli-plugin-unit-jest": "^4.0.5",
    "@vue/test-utils": "1.0.0-beta.29",
    "jest": "^24.9.0",
    // ...
}
复制代码

项目配置

// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
  preset: '@vue/cli-plugin-unit-jest',
  automock: false,
 "/private/var/folders/10/bb2hb93j34999j9cqr587ts80000gn/T/jest_dx",
  clearMocks: true,
  // collectCoverageFrom: null,
  coverageDirectory: 'tests/coverage'
  //...
}
复制代码

单元测试

测试 utils 工具类

对咱们以前写的一个性别名称转换工具进行测试

import { convertIdToName } from './convertIdToName'
describe('测试convertIdToName方法', () => {
  const list = [
    { id: 0, name: '男' },
    { id: 1, name: '女' },
    { id: 2, name: '未知' }
  ]
  it('测试正常输入', () => {
    const usage = list
    usage.forEach((item) => {
      expect(convertIdToName(item.id, list)).toBe(item.name)
    })
  })
  it('测试非正常输入', () => {
    const usage = ['a', null, undefined, NaN]
    usage.forEach((item) => {
      expect(convertIdToName(item, list)).toBe('')
    })
  })
})
复制代码

这样一测试,发现原来咱们以前写的工具备这么多漏洞

测试正常输入所有经过了,非正常输入失败了,根据测试用例改进咱们的代码

export const convertIdToName = (value, list) => {
  if (value !== 0 && value !== 1 && value !== 2) return ''
  const item = list.find(function(item) {
    return item.id === value
  })
  return item.name
}
复制代码

如今测试都经过了呢

测试 components 单文件组件

对咱们最简单的 hello world 进行测试

<template lang="pug">
  .hello
    h1 {{ msg }}
</template>
<script> export default { props: { msg: String } } </script>

复制代码
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()).toMatch(msg)
  })
})
复制代码

测试 api 请求

异步测试有几种常见写法

  • asyncawait
  • done()

简单的异步测试,测试一个简单的登录请求

export const login = (data) => post('/user/login', data)
复制代码

测试代码

import { login } from '@/api/index'
describe('login api', () => {
  const response = {
    code: '1000',
    data: {}
  }
  const errorResponse = {
    code: '5000',
    data: {},
    message: '用户名或密码错误'
  }
  it('测试正常登录', async () => {
    const params = {
      user: 'admin',
      password: '123456'
    }
    expect(await login(params)).toEqual(response)
  })
  it('测试异常登录', async () => {
    const params = {
      user: 'admin',
      password: '123123'
    }
    expect(await login(params)).toEqual(errorResponse)
  })
})
复制代码

功能模块测试

组件,api,工具这些零零碎碎都测试了,并且这些都是比较通用、和业务关系不大的代码,它们改动较少,测试到这里其实已经足够了,已经达到了 20% 的测试工做量了很大一部分代码的目的。

为何我说只有 20% 的工做量呢?由于这些都是不怎么变化的逻辑,是一劳永逸的事情。长远来讲占用的工做量确实不多。

可是有些状况业务仍是必须得测,也就是必需要功能模块集成测试。

常常得回归的业务,那种迭代对原有的系统比较大,避免改动以后使旧的代码各类新的问题。这种常常回归测试,采用 BDD + 集成测试,比不停改 bug 要轻松的多。

快照测试

像版本同样,每次测试以后生成一个版本,比较与上一个版本的区别。 这是一种粒度及其小的测试,能够测试到每个符号。

好比我用它来测试一个配置文件的变更

这是咱们一个配置文件

export const api = {
  develop: 'http://xxxx:8080',
  mock: 'http://xxxx',
  feature: 'http://xxxx',
  test: 'http://xxxx',
  production: 'http://xxxx'
}
export default api[process.env.NODE_ENV || 'dev']
复制代码

使用快照测试

import { api } from './config'

describe('配置文件测试', () => {
  it('测试配置文件是否变更', () => {
    expect(api).toMatchSnapshot({
      develop: 'http://xxxx:8080',
      mock: 'http://xxxx',
      feature: 'http://xxxx',
      test: 'http://xxxx',
      production: 'http://xxxx'
    })
  })
})

复制代码

使用快照第一次测试后,经过测试,代码被写入快照

改动配置再次测试,未经过测试
这时若是要改变配置怎么办呢? 只需同时改一下用例就行了。快照将再次写入快照生成版本2,这样配置改动也有根据了

TDD 与 BDD

最近讨论比较多的算是测试驱动开发行为驱动开发,其实总得来讲是 4 种

  1. 不写测试。好处是省时间,坏处固然就 bug 多,代码质量低。
  2. 先写测试和测试用例,再写代码,也就是测试驱动开发。这样好处是代码比较健全,考虑的因素比较多。固定模块健壮性高,bug少。
  3. 先写代码,再经过模拟用户的行为写测试。好处是思路清晰,若是测试用例够全面,基本上线基本没太大问题,回归测试也很好作。
  4. 写代码以前写测试和用例,写完代码以后再写用户行为测试。这种代码质量就过高了,固然缺点就是费时间。

那么你是哪种? 反正我比较佛系哈,有的不写测试,也有的写满测试。

总结

本篇文章耗费做者一个多星期的业余时间,存手工敲打 6000+字,同时收集,整理以前不少技巧和边写做边思考总结。若是能对你有帮助,即是它最大的价值。都看到这里还不点赞,太过不去啦!😄

因为技术水平有限,文章中若有错误地方,请在评论区指出,感谢!

文中大多数代码将在suo-design-pro 中更新

项目有时间会尽可能完善

写实践总结性文章真的很耗费时间。如何文章中有帮到你的地方分享下呗,让更多人看到!

相关文章
相关标签/搜索