Vue 应用单元测试的策略与实践 05 - 测试奖杯策略

本文首发于 Vue 应用单元测试的策略与实践 05 - 测试奖杯策略 | 吕立青的博客javascript

欢迎关注知乎专栏 —— 前端的逆袭(凡可 JavaScript,终将 JavaScript。)html

欢迎关注个人博客知乎GitHub掘金前端


本文的目标

  1. Vue 项目中测试收益如何最大化,如何配置高性价比的测试策略,即什么地方最该花力气测试,什么地方又能够暂且放一放?
// Given
一个具有UT基础但找不到着力点的求索之徒🐒
// When
当他🚶阅读本文的Vue应用测试策略部分
// Then
他可以找到测试的重点,从新燃起对UT的热情🔥
他可以在项目背景下合理配置单元测试的测试策略
复制代码

单元测试的特色及其位置

前言从敏捷:团队和企业的高响应力谈到单元测试,可能有同窗会问,高响应力这个事情我承认,也承认快速开发的同时,质量也很重要。可是,为了达到“保障质量”的目的,不必定得经过测试呀,就算须要测试,也不必定得经过单元测试。vue

这是一个好的问题。为了达到保障质量这个目标,测试固然只是其中一个方式,稳定的自动化部署、集成流水线、良好的代码架构、甚至于团队架构的必要调整等,都是必须跟上的基础设施。自动化测试不是解决质量问题的银弹,多方共同提高才可能起到效果。java

即使咱们谈自动化测试,也未必所有都是单元测试。咱们对自动化测试套件寄予的厚望是,它能帮咱们安全重构已有代码快速回归已有功能保存业务上下文。测试种类多种多样,为何咱们要重点谈单元测试呢?缘由很简单,由于它写起来相对最容易、运行速度最快、反馈效果又最直接。git

测试奖杯🏆:软件测试的分层策略

测试奖杯(Testing Trophy)是一种自下而上的 Web 应用测试策略。其实这是在说咱们须要编写_恰到好处的_测试,给予团队足够的信心 —— 正确的测试,而_不是_仅仅追求达到100%的测试覆盖率而已。程序员

测试奖杯的四个部分

使用测试奖杯策略,咱们能够将这些自动化测试技术进行分层:github

  • 使用静态类型系统和linter 来捕获拼写或语法之类的基本错误。
  • 编写有效单元测试 须要特别针对于应用的某些关键行为或功能。
  • 编写集成测试 以确保 Web 应用各模块之间可以正常协调工做。
  • 建立端到端(e2e)功能测试 对关键路径进行自动化点击操做,而不是等到最终用户来发现问题。

这种四层自动化测试提供了多快好省(放心、快速、省钱)的 JavaScript 专业化测试,最大的特色是它可以反复执行且收益递增,即不须要彻底采纳就能得到收益,立马见效。vuex

性价比最高的单元测试

所以,咱们须要有策略性地根据收益-成本的原则,考虑项目的实际状况和痛点来定制测试策略:好比三方依赖多的项目能够多写些契约测试,业务场景多、复杂或常常回归的场景能够多写些端到端测试,等。但不论如何,整个测试奖杯体系中,你仍是应该拥有更多低层次的单元测试,由于它们成本相对最低,运行速度最快(一般是毫秒级别),而对单元的保护价值相对更大。数据库

Vue 应用测试的测试策略

一个常见的 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% 覆盖

Component 的测试标准

组件测试实际上是前端测试中实践最多,但各方见解最不统一的地方,这也是先后端在谈论单元测试时最大的分歧所在。Vue 组件是一个高度自治的单元,从分类上来看,它大概有这么几类:

  • 展现型业务组件
  • 容器型业务组件
  • 通用 UI 组件
  • 功能型组件

对于 Vue 组件测什么不测什么有一些判断标准:除去功能型组件,其余类型的组件通常是以渲染出一个语法树 render() 为终点的,它描述了页面的 UI 内容、结构、样式和一些逻辑 component(props) => UI。内容、结构和样式,比起测试,直接在页面上调试反馈效果更好。测也不是不行,但都不免有不稳定的成本在;逻辑这块,有一测的价值,但须要控制好依赖。综合上面提到的测试原则进行考虑,个人建议是:两测两不测。

  • 组件分支渲染逻辑必须测
  • 事件调用和参数传递通常要测
  • 链接 vuex 的高阶 SMART 组件不测
  • 渲染出来的 UI 不在单元测试层级测

总结一下,其实每种组件都要测渲染分支事件调用,跟组件类型根本没必然的关联…

组件类型 / 测试内容 分支渲染逻辑 事件调用 @connect 纯 UI
展现型组件
容器型组件
通用 UI 组件
功能型组件

单元测试的 F.I.R.S.T 原则

编写容易维护的单元测试有一些原则,这些原则对于任何语言、任何层级的测试都适用。这些原则不是新东西,但老是须要时时温故知新,前人总结成 F.I.R.S.T 五个原则,以此为镜,能够时时检验你的单元测试是否高效:

  • F Fast:测试须要频繁运行,所以要能快速运行;
  • I Independent:测试应该相互独立,一次只测一条分支;
  • R Repeatable:测试自己不包含逻辑,能在任何环境中重复;
  • S Self-validating:只关注输入输出,不关注内部实现;
  • T Timely:测试应该及时编写,表达力极强,易于阅读;

Fast:运行速度快,频繁运行

单元测试只有在毫秒级别内完成,开发者才会愿意频繁地运行它,将其做为快速反馈的手段也才能成立。那么为了使单元测试更快,咱们须要:

  • 尽量地避免依赖。除了恰当设计好对象,关于避免依赖我已知有两种不一样的见解:
    • 使用mock适当隔离掉三方的依赖(如数据库、网络、文件等)
    • 避免mock,换用更快速的数据库、启动轻量级服务器、重点测试文件内容等来迂回
  • 将依赖、集成等耗时、依赖三方返回的地方放到更高层级的测试中,有策略性地去作

Independent:一次只测一条分支

一般来讲,一条分支就是一个业务场景,是作任务分解(Tasking)过程的一个细粒度的task。为何测试只测一条分支呢?很显然,如此你才能给它一个好的描述,这个测试才能保护这个特定的业务场景,挂了的时候能给你细致到输入输出级别的业务反馈。

常见的反模式是,实现自己就作了太多的事情,不符合单一功能(SRP)原则。若是你发现某个模块的单元测试特别难写的话,那么这个模块的实现自己或输入/输出就足够繁琐,应看成为一种某味道识别出来进行重构。

Repeatable:测试不包含逻辑

跟写声明式的代码同样的道理,测试须要都是简单的声明:准备数据、调用函数、断言,让人一眼就明白这个测试在测什么。若是含有逻辑,你读的时候就要多花时间理解;一旦测试挂掉,你咋知道是实现挂了仍是测试自己就挂了呢?特别是对于一些时间或者随机数相关的测试,必定不可以从测试中随机生成这样的测试数据,保证测试中不包含任何过多的逻辑。

但对于一些项目中的 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)
  }
)
复制代码

固然,对纯数据驱动的测试,也有一些不一样的见解,认为这样可能丢失一些描述业务场景的测试描述。因此这种方式还主要看项目组的接受度。

Self-validating:只关注输入输出,不关注内部实现

好比购物车“计算总价格”这样的一个功能,测试自己不关注内部实现:你能够用reduce实现,也能够本身写for循环实现。只要测试输入没有变,输出就不该该变。这个特性,是测试支撑重构的基础。由于重构指的是,在不改变软件外部可观测行为的基础上,调整软件内部的实现。

另外,还有一些测试实现代码的执行次序。这也是一种“关注内部实现”的测试,这就使得除了输入输出外,还有“执行次序”这个因素可能使测试挂掉。显然,这样的测试也不利于重构的开展。

此外,对外部依赖采起mock策略,一样是某种程度上的“关注内部实现”,由于mock的失败一样将致使测试的失败,而非真正业务场景的失败。对待mock的态度,肖鹏有篇文章Mock的七宗罪对此展开了详细描述,应当谨慎使用。

Timely:表达力极强,易于阅读

测试应该及时编写,只有在当下最熟悉业务的时候,才可以写出表达力最强的测试。而当咱们在将来不当心破坏某个功能时,表达力强的测试才能在失败的时候给你很是迅速的反馈。它讲的是两方面:

  • 看到测试时,你就知道它测的业务点是啥
  • 测试挂掉时,能清楚地知道失败的业务场景、指望数据与实际输出的差别

总结起来,这些表达力主要体如今如下的方面:

  • 测试描述。遵循上一条原则(一个单元测试只测一个分支)的状况下,描述一般能写出一个至关详细的业务场景。这为测试的读者提供了极佳的业务上下文
  • 测试数据准备。无关的测试数据(好比对象中的不少无关字段)不该该写出来,应只准备能体现测试业务的最小数据
  • 输出报告。选用断言工具时,应注意除了要提供测试结果,还要能准确提供“指望值”与“实际值”的差别

上述第三点有些测试框架提供了反例,好比说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)。

懒惰:是这样一种品质,它使得你花大力气去避免消耗过多的精力。它敦促你写出节省体力的程序,同时别人也能利用它们。为此你会写出完善的测试或文档,以避免别人问你太多问题。

想象一下,将测试软件的繁重工做所有外包给机器。

你是开发工程师呀,这个时代最伟大的脑力工做者啊!你知道人类在处理重复性任务的时候都很糟糕,可是你还知道**_机器_很是很是擅长复杂的重复性任务。**更专业的开发人员就是会使用计算机来作自动化测试 —— 一成天都在绵绵不休地进行,帮你处理这些测试软件的繁重工做。

  • 自动化测试是专业的。
  • 自动化测试是你的后盾,是你的肌肉。
  • 自动化测试是你的秘密武器……

时不时,问一下本身这几个问题:

  • ,还能够如何偷懒?
  • 应该让计算机帮忙测点什么?
  • 计算机该在何时进行测试?
  • 须要100%的覆盖率吗?
  • 多少次测试就足够了?

未完待续……

## 单元测试基础

  • ### 单元测试与自动化的意义
  • ### 为何选择 Jest
  • ### Jest 的基本用法
  • ### 该如何测试异步代码?

## Vue 单元测试

  • ### Vue 组件的渲染方式
  • ### Wrapper find() 方法与选择器
  • ### UI 组件交互行为的测试

## Vuex 单元测试

  • ### CQRS 与 Redux-like 架构
  • ### 如何对 Vuex 进行单元测试
  • ### Vue组件和Vuex store的交互

## Vue 应用测试策略

  • ### 单元测试的特色及其位置
  • ### 测试奖杯🏆:软件测试的分层策略
  • ### 单元测试的F.I.R.S.T原则

## Vue 单元测试的落地

  • ### 应用测试策略落地的几点建议

您可能也会喜欢:

相关文章
相关标签/搜索