本文首发于 Vue 应用单元测试的策略与实践 05 - 测试奖杯策略 | 吕立青的博客javascript
欢迎关注知乎专栏 —— 前端的逆袭(凡可 JavaScript,终将 JavaScript。)html
// Given
一个具有UT基础但找不到着力点的求索之徒🐒
// When
当他🚶阅读本文的Vue应用测试策略部分
// Then
他可以找到测试的重点,从新燃起对UT的热情🔥
他可以在项目背景下合理配置单元测试的测试策略
复制代码
前言从敏捷:团队和企业的高响应力谈到单元测试,可能有同窗会问,高响应力这个事情我承认,也承认快速开发的同时,质量也很重要。可是,为了达到“保障质量”的目的,不必定得经过测试呀,就算须要测试,也不必定得经过单元测试。vue
这是一个好的问题。为了达到保障质量这个目标,测试固然只是其中一个方式,稳定的自动化部署、集成流水线、良好的代码架构、甚至于团队架构的必要调整等,都是必须跟上的基础设施。自动化测试不是解决质量问题的银弹,多方共同提高才可能起到效果。java
即使咱们谈自动化测试,也未必所有都是单元测试。咱们对自动化测试套件寄予的厚望是,它能帮咱们安全重构已有代码、快速回归已有功能、保存业务上下文。测试种类多种多样,为何咱们要重点谈单元测试呢?缘由很简单,由于它写起来相对最容易、运行速度最快、反馈效果又最直接。git
测试奖杯(Testing Trophy)是一种自下而上的 Web 应用测试策略。其实这是在说咱们须要编写_恰到好处的_测试,给予团队足够的信心 —— 正确的测试,而_不是_仅仅追求达到100%的测试覆盖率而已。程序员
使用测试奖杯策略,咱们能够将这些自动化测试技术进行分层:github
这种四层自动化测试提供了多快好省(放心、快速、省钱)的 JavaScript 专业化测试,最大的特色是它可以反复执行且收益递增,即不须要彻底采纳就能得到收益,立马见效。vuex
所以,咱们须要有策略性地根据收益-成本的原则,考虑项目的实际状况和痛点来定制测试策略:好比三方依赖多的项目能够多写些契约测试,业务场景多、复杂或常常回归的场景能够多写些端到端测试,等。但不论如何,整个测试奖杯体系中,你仍是应该拥有更多低层次的单元测试,由于它们成本相对最低,运行速度最快(一般是毫秒级别),而对单元的保护价值相对更大。数据库
一个常见的 Vue 应用会包括这么几个层面:组件、数据管理、Vuex、反作用等等,对于不一样的项目应该有必定的适应性。Vue + Vuex 架构中的不一样元素有不一样的特色,所以即使是单元测试,咱们也会有针对性的测试策略:
架构层级 | 测试内容 | 测试策略 | 解释 |
---|---|---|---|
action 层 | 1. 是否获取了正确的参数 2. 是否正确地调用了 API 3. 是否使用了正确的返回值存取回 Vuex 中 4. 业务分支逻辑 5. 异常逻辑 |
这五个业务点建议 100% 覆盖 | 这个层级主要包含前述 5 大方面的业务逻辑,进行测试颇有重构价值 |
mutation 层 | 是否正确完成计算 | 有逻辑的 mutation 要求 100%覆盖率 | 这个层级输入输出明确,又包含业务计算,很是适合单元测试 |
getter 层 | 是否正确完成计算 | 有逻辑的 getter 要求 100%覆盖率 | 这个层级输入输出明确,又包含业务计算,很是适合单元测试 |
component 层 | 是否渲染了正确的组件 | 1. 组件的分支渲染逻辑要求100%覆盖 2. 交互事件的调用参数通常要求100%覆盖 3. 被 connect 过的组件不测 |
这个层级最为复杂,仍是以「代价最低,收益最高」为指导原则进行 |
UI 层 | 组件是否渲染了正确的样式 | 1. 纯 UI 不测 2. CSS 不测 |
这个层级以我目前理解来讲测试较难稳定,成本又较高 |
utils 层 | 各类辅助工具函数 | 没有反作用的必须 100% 覆盖 |
组件测试实际上是前端测试中实践最多,但各方见解最不统一的地方,这也是先后端在谈论单元测试时最大的分歧所在。Vue 组件是一个高度自治的单元,从分类上来看,它大概有这么几类:
对于 Vue 组件测什么不测什么有一些判断标准:除去功能型组件,其余类型的组件通常是以渲染出一个语法树 render()
为终点的,它描述了页面的 UI 内容、结构、样式和一些逻辑 component(props) => UI
。内容、结构和样式,比起测试,直接在页面上调试反馈效果更好。测也不是不行,但都不免有不稳定的成本在;逻辑这块,有一测的价值,但须要控制好依赖。综合上面提到的测试原则进行考虑,个人建议是:两测两不测。
总结一下,其实每种组件都要测渲染分支和事件调用,跟组件类型根本没必然的关联…
组件类型 / 测试内容 | 分支渲染逻辑 | 事件调用 | @connect |
纯 UI |
---|---|---|---|---|
展现型组件 | ✅ | ✅ | – | ❌ |
容器型组件 | ✅ | ✅ | ❌ | ❌ |
通用 UI 组件 | ✅ | ✅ | – | ❌ |
功能型组件 | ✅ | ✅ | ❌ | ❌ |
编写容易维护的单元测试有一些原则,这些原则对于任何语言、任何层级的测试都适用。这些原则不是新东西,但老是须要时时温故知新,前人总结成 F.I.R.S.T 五个原则,以此为镜,能够时时检验你的单元测试是否高效:
单元测试只有在毫秒级别内完成,开发者才会愿意频繁地运行它,将其做为快速反馈的手段也才能成立。那么为了使单元测试更快,咱们须要:
一般来讲,一条分支就是一个业务场景,是作任务分解(Tasking)过程的一个细粒度的task。为何测试只测一条分支呢?很显然,如此你才能给它一个好的描述,这个测试才能保护这个特定的业务场景,挂了的时候能给你细致到输入输出级别的业务反馈。
常见的反模式是,实现自己就作了太多的事情,不符合单一功能(SRP)原则。若是你发现某个模块的单元测试特别难写的话,那么这个模块的实现自己或输入/输出就足够繁琐,应看成为一种某味道识别出来进行重构。
跟写声明式的代码同样的道理,测试须要都是简单的声明:准备数据、调用函数、断言,让人一眼就明白这个测试在测什么。若是含有逻辑,你读的时候就要多花时间理解;一旦测试挂掉,你咋知道是实现挂了仍是测试自己就挂了呢?特别是对于一些时间或者随机数相关的测试,必定不可以从测试中随机生成这样的测试数据,保证测试中不包含任何过多的逻辑。
但对于一些项目中的 utils 来讲,咱们指望 util 都是纯函数,便是不依赖外部状态、不改变参数值、不维护内部状态的函数。因为可能是数据驱动,一个输入对应一个输出,而且不须要准备任何依赖,这使得它多了一种测试的选择,也便是参数化测试的方式。
参数化测试能够提高数据准备效率,同时依然能保持详细的用例信息、错误提示等优势。jest 从 23 后就内置了对参数化测试的支持,以下:
test.each([
[['0', '99'], 0.99, '(整数部分为0时也应返回)'],
[['5', '00'], 5, '(小数部分不足时应该补0)'],
[['5', '10'], 5.1, '(小数部分不足时应该补0)'],
[['4', '38'], 4.38, '(小数部分不足时应该补0)'],
[['4', '99'], 4.994, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['4', '99'], 4.995, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['4', '99'], 4.996, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['-0', '50'], -0.5, '(整数部分为负数时应该保留负号)'],
])(
'should return %s when number is %s (%s)',
(expected, input, description) => {
expect(truncateAndPadTrailingZeros(input)).toEqual(expected)
}
)
复制代码
固然,对纯数据驱动的测试,也有一些不一样的见解,认为这样可能丢失一些描述业务场景的测试描述。因此这种方式还主要看项目组的接受度。
好比购物车“计算总价格”这样的一个功能,测试自己不关注内部实现:你能够用reduce
实现,也能够本身写for
循环实现。只要测试输入没有变,输出就不该该变。这个特性,是测试支撑重构的基础。由于重构指的是,在不改变软件外部可观测行为的基础上,调整软件内部的实现。
另外,还有一些测试实现代码的执行次序。这也是一种“关注内部实现”的测试,这就使得除了输入输出外,还有“执行次序”这个因素可能使测试挂掉。显然,这样的测试也不利于重构的开展。
此外,对外部依赖采起mock策略,一样是某种程度上的“关注内部实现”,由于mock的失败一样将致使测试的失败,而非真正业务场景的失败。对待mock的态度,肖鹏有篇文章Mock的七宗罪对此展开了详细描述,应当谨慎使用。
测试应该及时编写,只有在当下最熟悉业务的时候,才可以写出表达力最强的测试。而当咱们在将来不当心破坏某个功能时,表达力强的测试才能在失败的时候给你很是迅速的反馈。它讲的是两方面:
总结起来,这些表达力主要体如今如下的方面:
上述第三点有些测试框架提供了反例,好比说chai和sinon提供的断言API就不如jest友好,体如今:
expect(array).to.eql(array)
出错的时候,只能报告说expect [Array (42)] to equal [Array (42)]
,具体是哪一个数据不匹配,根本没报告expect(sinonStub.calledWith(args)).to.be.true
出错的时候,会报告说expect false to be true
。废话,我还不知道挂了么,可是那个stub究竟被什么参数调用则没有报告事实上,没有人有时间。可是,不管如何:
你所开发的软件终将被测试。若是不是由你本身发现,那么就是由你的用户发现(💥Bug)。
Perl语言的发明人Larry Wall说,好的程序员有3种美德: 懒惰、急躁和傲慢(Laziness, Impatience and hubris)。
懒惰:是这样一种品质,它使得你花大力气去避免消耗过多的精力。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的测试或文档,以避免别人问你太多问题。
想象一下,将测试软件的繁重工做所有外包给机器。
你是开发工程师呀,这个时代最伟大的脑力工做者啊!你知道人类在处理重复性任务的时候都很糟糕,可是你还知道**_机器_很是很是擅长复杂的重复性任务。**更专业的开发人员就是会使用计算机来作自动化测试 —— 一成天都在绵绵不休地进行,帮你处理这些测试软件的繁重工做。
时不时,问一下本身这几个问题:
## 单元测试基础
## Vue 单元测试
find()
方法与选择器## Vuex 单元测试
Redux-like
架构## Vue 应用测试策略
## Vue 单元测试的落地