前言

再讲单元测试测什么之前,我们先了解下为什么需要要单元测试?

当编写项目的时刻,如果我们假设底层的代码是正确无误的,那么先是高层代码中使用了底层代码;然后这些高层代码又被更高层的代码所使用,如此往复。当基本的底层代码不再可靠时,那么必需的改动就无法只局限在底层。虽然你可以修正底层的问题,但是这些对底层代码的修改必然会影响到高层代码。于是,一个对底层代码的修正,可能会导致对几乎所有代码的一连串改动,从而使修改越来越多,也越来越复杂。从而使整个项目也以失败告终。

单元测试针对程序模块,进行正确性检验的测试。其目的在于发现各模块内部可能存在的各种差错。单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。

下面我们来聊聊在项目中单元测试应该测些什么?

以国内互联网的开发节奏,在前端业务项目中全面覆盖单元测试有时显得不太可行,主要是因为以下这些绊脚石:

  • UI 交互复杂,路径难以覆盖全面
  • 工期紧,开发对实践 TDD,BDD 所带来的长远效益没有信心
  • 产品经理们时不时打着「敏捷开发」的旗号改需求,使得刚刚辛辛苦苦写完的测试脚本完全作废

在这样的处境下,一味强调单元测试的逻辑覆盖率是没有太大意义的,明确在哪里应用单测的能取得最大的边际效益是更有意义的事情。

以下笔者根据自己的一些在单测的实战经验,列出了三项关于「单元测试应该测什么」的观点并附以一些例子与大家交流:

单元测试并非测试的全部

拿来主义地对待单元测试

单测只是一种局部模块测试,是诸多测试方案中的一种,认识到这一点可以避免我们为了测试而测试,或者为了指标而测试。

同时也应该认识到单测本身的覆盖能力也是有限的,全部用例的 PASS 和 100% 的覆盖率都不能保证被测试模块的所有逻辑路径都有正确的行为。

是否对一个模块使用单元测试往往取决于这个模块的逻辑稳定性和业务类型

例如对于一个底层 npm 包项目,单元测试几乎是他唯一的代码质量保障手段,这时就应该尽可能通过单元测试验证它在各种应用场景下的行为是否符合预期,来最低成本地保证它每次发包和更新的质量。对这类项目,彻底应用 BDD 开发模式也会获得越来越高的开发效率收益。

而对于一个功能复杂的 UI 组件,除了单元测试,还有 E2E 测试,自动化回归测试,QA 手动测试(:blush:)来保障它的代码质量。此时使用单元测试的边际效益可能不是最高的,可以考虑通过别的手段来回归它的逻辑。也可以考虑在初版功能验证上线后通过快照测试(snapshot)来回归验证每一次迭代的逻辑。

边界环境的模拟

让模块穿梭时空

单测的一个很重要的意义是帮助我们在开发阶段模拟出 QA 手动测试(:blush:)甚至线上使用场景下都不易触达的边界场景,如:

  • 模拟个别浏览器下的 JS 版本
  • 模拟某个 URL 状态
  • 模拟某种本地缓存状态
  • 模拟不同时区下的情形
  • 模拟时间过了一个小时(这几乎只有单元测试能够做到)

等等

使用这类模拟对模块进行单元测试的边际效益是极高的,往往比 QA 去作等价的模拟快得多。

比如下面这段脚本,通过 jest 的 timer mock 能力,实现了对 expire 函数的测试:

const expire = (callback) => setTimeout(callback, 60000); // 一分钟以后过期test('到点就调用回调', () => {const callback = jest.fn();expire(callback);jest.advanceTimersByTime(59999);expect(callback).not.toBeCalled();jest.advanceTimersByTime(1);expect(callback).toBeCalledOnce();
})

这段代码通过 jest.advanceTimersByTime 精确模拟了宏任务的运行过程,同步完成了原本需要一分钟才能验证一次的异步流程的测试。

又比如下面的测试脚本用来测试一个名为 catchFromURL 的工具函数,该函数可以从当前的 URL 中获取指定的参数作为返回值返回,同时从 URL 中抹去该参数。

这中需求通过 URL 携带 token 信息的业务场景(如单点登录)中是非常常见的。

test('通过URL获取指定的参数值并抹去之', () => {const CURRENT_ORIGIN = document.location.origin;const testHref = `${CURRENT_ORIGIN}/list/2/detail?a=123b&b=true#section2`;history.replaceState(null, '', testHref);expect(catchFromURL('a')).toBe('123b');expect(document.location.href).toBe(`${CURRENT_ORIGIN}/list/2/detail?b=true#section2`);
})

这段测试代码通过 jsdom 来实现对需要测试的环境的模拟。环境的构造和模拟其实是单元测试中的一个难点,由于 jsdom 本身的一些缺陷(如没有实现 Navigator)使得在测试脚本运行的 node 环境中模拟正确的浏览器环境往往需要用到很多的 Hack 技术,这一点在未来的夜点心中会着重中展开讨论。

点到为止

less is more

测试代码无需关心被测试模块的具体实现,点到为止地测试几种必要的流程场景即可。这一方面可以减少写测试逻辑的时间,一方面可以使得业务逻辑具有更大的实现自由度。

对一个业务模块,测试脚本只需要关心该模块所关联的所有外部性即可:

  • 对于函数模块而言,控制它引用的模块、它的输入和它的副作用,验证它的输出和对副作用的影响
  • 对于组件模块而言,控制它依赖的服务、它依赖的子组件、它的 props和它的事件,验证它的渲染结果和 props 中回调的调用情况,而不应该关心它的 state。

下面的脚本通过 enzyme 组件测试工具测试了一个名为 ValidatableInput 的 React 组件。这个组件在失焦(blur)时会触发 onValidate 回调,并传入 inputValue 参数。

test('失焦时触发 onValidate', () => {const onValidate = jest.mock();const inputValue = '输入的内容';const wrapper = shallow(<ValidatableInputplaceholder={''}value={inputValue}alert={''}onChange={onChange}onValidate={onValidate}/>);wrapper.find('.validatable-input').first().simulate('blur');expect(onValidate).toBeCalledWith(inputValue);});

在上述测试用例中我们的测试逻辑完全基于行为开展,只关心失焦的「动作」和执行回调的「反馈」,没有去断言任何关于组件状态的内容。

这样组件可以根据它的需要自由地实现它的内部逻辑,例如添加通过外部的 Provider 来提供 value 和 onChange 成为受控组件的能力。这些实现的变化都不会影响当前这条测试用例的有效性。

上面就是一些对应该用单元测试测什么的看法,把单测用在它最擅长的地方,才能在紧凑的开发节奏中取得事半功倍的效果。

结语

更多软件测试资料视频教程需要的可以私信我关键词(资料)免费获取哦!

欢迎留言,或是关注我的专栏和我交流。

在项目中单元测试是用来做什么的?相关推荐

  1. 现代IT项目中的需求管理如何做?

    现代IT项目中的需求管理如何做? 领测软件测试网 我们知道现代项目管理的六要素是:时间.成本.质量.组织.范围.客户满意度,实际上,要满足这六个要素,计划一个良好的需求分析是实现这六因素的前提,如果我 ...

  2. 软考高频考点——项目中标了以后该怎么做?

    2022年下半年软考全国各地陆陆续续已经截止报名了,很多考友已经进入了热火朝天的备考阶段. 最近发现很多考友都是第一次考软考,没有太多基础和备考经验,所以,今天小编特意为考友们分享软考其中一个高频考点 ...

  3. 类项目中的配置文件app.config在打包安装后的信息获取的问题

    在一个项目中碰到这样的一个问题,做一个WORD插件,功能在类库项目中实现了,配置信息存在类库项目的配置文件app.config中,在进行打包后,获取的配置文件中的DocType节点信息时,使用以下方法 ...

  4. 如何在Visual Studio项目中正确添加汇编代码 .

    引用注明>> [作者:张佩][镜像:www.yiiyee.cn/blog] 1.      问题描述 在以往的编程经历中,本人最常使用的汇编代码是__asm {int 3}.它可以在我的代 ...

  5. Spring项目中使用webservice实现h5的websocket通信

    一.在项目中建立一个webservice来做后台操作. package org.calonlan.soulpower.websocket;import java.text.SimpleDateForm ...

  6. lerna 项目中集成 babel lint-staged husky eslint

    lerna 项目中集成 babel lint-staged husky eslint Monorepo 是针对单仓库.多 package 的流行解决方案, lerna 是它的一种实现. 说明 重要 p ...

  7. 项目中如何避免团队成员相互甩锅?

    见过一些团队,出了问题就甩锅,问题推来推去,半天没人解决,从而影响了项目的进度,伤害了团队的士气. 一.工作职责不明确 甩锅,这种情况大多是工作职责范围不明确导致的.那么我们如何确定工作职责范围呢? ...

  8. 在vue项目中使用svg图标

    目录 VUE项目中为什么使用svg 1.在src/components下创建文件夹,命名为SvgIcon,并再SvgIcon文件夹下,新增目录index.vue文件: 2.在src目录下,新增文件夹, ...

  9. vue-cli+webpack 的项目中怎么导入bootstrap与jquery

    vue-cli+webpack 的项目中怎么导入bootstrap与jquery 虽然vue.js中有很多jquery的效果,引入jquery的好处我认为有两点: 1.很多前端工程师都是从学jquer ...

最新文章

  1. 77GHz 和24GHz Radar性能解析
  2. 计算机学业水平测试题及答案初中,初中信息技术学业水平测试——选择题
  3. asmcmd:Connected to an idle instance.
  4. python怎么把数据存在本地_将Python中的数据存储到系统本地的简单方法
  5. 怎么用鼠标选中java中table的某一行_为什么同事的工作效率那么高?学会这些鼠标双击技巧,你也可以的...
  6. linux服务器终止进程,结束linux 服务器系统中一个程序的多个进程
  7. script片段在前导致对下文的html元素引用失效
  8. 【2017-3-17】视图,事务,备份还原,分离附加
  9. 垃圾分类小程序,云开发 (附源码)
  10. MFC 通用对话框之字体对话框
  11. mui+hbuilder 将图片压缩并转变为base64 与改变图片尺寸大小来减小图片文件大小
  12. 【重识云原生】第四章云网络4.8.4节——OpenStack与SDN的集成
  13. 阿里巴巴(菜鸟) - 算法工程师(机器学习)提前批笔试面试总结
  14. 计算机电源MOD,电源全模组和非模组究竟有什么区别?
  15. 基于Ubuntu9.10 雨林木风Linux Y1.5发布
  16. (*^__^*) 感恩相伴,祝福相随 (*^__^*)
  17. 维基百科图片无法正常显示
  18. 【品牌搜索】两步查找品牌数据,快速实现品牌营销布局。
  19. 无损数据动态磁盘逆转为基本磁盘的方法
  20. 第22课:打包和发布 Electron 应用

热门文章

  1. 【Rust日报】 2019-06-16:用 Rust, Haskell, C++, Python, Scala 和 OCaml 实现同一个工程的比较...
  2. Protege使用教程(进阶篇)
  3. 饥荒联机云服务器_WeGame饥荒联机版专用服务器多层世界搭建教程
  4. 总结利用秩为1的矩阵相关矩阵的秩的计算问题
  5. matlab中webcam,MATLAB编程-MATLAB2014a的webcam操作
  6. DirectX游戏编程入门——第一部分(Windows和DirectX游戏编程引言)——认识Windows
  7. 401状态码的含义和处理
  8. SPA项目之登录注册
  9. python内置库求复数的辐角_根据下列选项,回答 30~34 题: A.杜仲B.黄柏C.厚朴D.肉桂E.牡丹皮 第 30 题 断面较平坦,粉...
  10. 批处理使用问题处理(逐步添加)