什么是单元测试,如何去写⼀个单元测试
相信单元测试是属于那种没有⽤过也听过的技术(如果你是⼤佬,听过也⽤过,欢迎提出宝贵的意见 ♀
♂ )。那么到底什么是单元测试,单元测试在实际项⽬开发中能给我们带来什么样的好处?我们站在前端开发的⾓度⼀起来聊⼀聊单元测试。
(⼀)什么是单元测试
单元测试概念
单元测试是指对软件中最⼩可测单元进⾏检查和验证;c语⾔中单元指⼀个函数,java中指⼀个类。图形化软件中可以指⼀个窗⼝或者⼀个菜单。总的来说,单元就是认为规定最⼩的被测试模块。
这个便是对百度百科上对单元测试的介绍,那么对于我们前端来说单元测试是⽤来对⼀个模块、⼀个函数或者⼀个类来进⾏正确性检验的测试⼯作。
⼤多数单元测试包括四个主体:
测试套件describe、
测试⽤例it、
判定条件expect、
断⾔结果toEqual。
什么不是单元测试
在了解了什么是单元测试的基础上,那么什么不是单元测试呢?在《修改代码艺术》⼀书上有这样的介绍:
需要访问数据库的测试不是单元测试
需要访问⽹络的测试不是单元测试
需要访问⽂件系统的测试不是单元测试
以上便是对单元测试概念的简单介绍,那么为什么要使⽤单元测试,单元测试有什么优势,不考虑回报的程序员不是好的程序员。
(⼆)单元测试对我们开发程序有什么好处
⾸先是⼀个前端单元测试的根本性原由:JavaScript 是动态语⾔,缺少类型检查,编译期间⽆法定位到错误; JavaScript 宿主的兼容性问题。⽐如 DOM 操作在不同浏览器上的表现。
正确性:测试可以验证代码的正确性,在上线前做到⼼⾥有底。
⾃动化:当然⼿⼯也可以测试,通过console可以打印出内部信息,但是这是⼀次性的事情,下次测试还需要从头来过,效率不能得到保证。通过编写测试⽤例,可以做到⼀次编写,多次运⾏。
解释性:测试⽤例⽤于测试接⼝、模块的重要性,那么在测试⽤例中就会涉及如何使⽤这些API。其他开发⼈员如果要使⽤这些API,那阅读测试⽤例是⼀种很好地途径,有时⽐⽂档说明更清晰。
驱动开发,指导设计:代码被测试的前提是代码本⾝的可测试性,那么要保证代码的可测试性,就需要在开发中注意API的设计,TDD 将测试前移就是起到这么⼀个作⽤。
保证重构:互联⽹⾏业产品迭代速度很快,迭代后必然存在代码重构的过程,那怎么才能保证重构后代码的质量呢?有测试⽤例做后盾,就可以⼤胆的进⾏重构。
我们知道 ⾼覆盖率的单元测试,可以保证每次上线bug率⼤⼤降低,也是代码重构的基础。很多⽼项⽬,开发⼈员离职、新接⼿的⼈员不敢重构,慢慢称为团队负担、⼜不能下线,就是因为没有单元测试,改⼀点都怕出现不可测的bug。简单来说,也可以概括为以下⼏点
1. 提⾼代码质量
2. 减少bug,快速定位bug
3. 放⼼地修改、重构
4. 单元测试不但会使你的⼯作完成得更轻松。⽽且会令你的设计会变得更好,甚⾄⼤⼤减少你花在 调试上⾯的时间
(三)如何编写单元测试⽤例
如何编写单元测试⽤例,单元测试⽤例的原则是什么:
测试代码时,只考虑测试,不考虑内部实现;
数据尽量模拟现实,越靠近现实越好,
充分考虑数据的边界条件下·
对重点、复杂、核⼼代码、重点测试
利⽤AOP(⾯向切⾯编程),减少测试代码,避免⽆⽤功能
测试、功能开发相结合,有利于设计和代码重构
插⼀个⼩知识点:那么这⾥提到的AOP是什么意思,AOP是Aspect Oriented Program的⾸字母缩写意思是⾯向切⾯编程,这种在运⾏时,动态地将代码切⼊到类的指定⽅法、指定位置上的编程思想就是⾯向切⾯的编程。
⼀般⽽⾔,我们管切⼊到指定类指定⽅法的代码⽚段称为切⾯,⽽切⼊到哪些类、哪些⽅法则叫切⼊点。有了AOP,我们就可以把⼏个类共有的代码,抽取到⼀个切⽚中,等到需要时再切⼊对象中去,从⽽改变其原有的⾏为。
再简单了解了单元测试之后我们将其带⼊到我们实际项⽬开发中,来尝试⼀下
(四)组件化后,组件哪部分最具测试价值?(以React为例)
1. Component
Component 应着重关注render以及副作⽤,同时业务逻辑的处理过程,都应该尽量提取到Hooks和Utils⽂件中。因此,对于Component的测试,我们完全可以将重⼼主要放在以下这两⽅⾯问题上:
组件是否正常渲染了?
组件副作⽤是否正常处理了?
2. Hooks
如何测试React Hooks,社区⽬前已有相对成熟的解决⽅案,即@testing-library/react-hooks + react-test-renderer[2]。通过这两个依赖,开发⼈员可以很轻松的mock出Hooks执⾏所依赖的环境,把store的数据当作hooks的输⼊,关注在hooks内的业务逻辑,即可把Hooks当作纯⽅法(Pure Function)来进⾏测试。
3. Redux
对于Redux,如果项⽬在使⽤ Redux Toolkit 的话,事情会简单很多,开发⼈员只需要关注Dispatch的Actions即可。但如果Actions和Reducer是分开编写,则需要针对性处理
4. Service
不同项⽬或团队对Service的定义各不相同,这⾥我们要聊的主要指负责处理HTTP请求的request和response,以及相应的异常处理的数据层。Service主要的功能是对接Action,因⽽理想情况下Service只需要包含与API通信的代码,这种情况下,UT可有可⽆。但⼀些场景下,如果项⽬中没有使⽤BFF承担数据处理的⾓⾊,后端也没能提供完全符合前端数据结构需求的接⼝时,不可避免的,开发⼈员需要在此处完善数据处理的逻辑,以便获取清洗或聚合后的数据,因⽽这种情况下,UT覆盖是⾮常有必要的。
5. Utils/Helpers
Utils/Helpers主要包含以下⼏类类型:
数据结构的转化,各种convert⼯具函数
数据结构的处理,⽐如数据提取、合并压缩、整理⼯具函数
公共的⼯具函数
根据我们⽬前的项⽬习惯,当⼀段逻辑需要在Utils/Helpers中实现时,那么它⼀定是纯函数,其中多数情况⼜会包含⼀定程度的数据处理逻辑,所以基本都需要UT覆盖
在了解了项⽬组件哪些部分最具有测试价值之后,我们就要上⼿了,跃跃欲试中 ♀ ♂
(五)如何让我们的测试⽤例更易编写、维护?
举个例⼦ ,先看代码,看不看得懂不重要 ♀ ,我们先来了解⼀下
// production code
const computeTotalAmount=(products)=>{
duce((total, product)=> total + product.price,0);
}
// testing code
it('should return summed up total amount 1000 when there are three products priced 200, 300, 500',()=>{
// given - 准备数据
const products =[
{ name:'nike', price:200},
{ name:'adidas', price:300},
{ name:'lining', price:500},
]
// when - 调⽤被测函数
const result =computeTotalAmount(products)
// then - 断⾔结果
expect(result).toBe(1000)
})
可以看到我们⾸先定义了⼀个computeTotalAmount的待测函数,it包裹了我们的测试⽤例。在测试⽤例中,⾸先第⼀步:准备数据,然后调⽤被测函数,最后输出断⾔结果。 可以看到这样的结果清晰明了。
好的单元测试应该遵循AAA的模式,AAA模式:编排(Arrange),执⾏(Act),断⾔(Assert)。可以让你写出⽐较清晰的测试结构,既易于阅读,也易于编写
编写单元有以下⼏个编写原则:
mock数据集中管理,考虑mock数据极端情况
只关注输⼊输出,不关注内部实现
⼀个单元测试只测⼀个业务场景
如此你才能给它⼀个好的描述,这个测试才能保护这个特定的业务场景,挂了的时候能给你细致到输⼊输出级别的 业务反馈。
表达⼒极强,不包含逻辑
表达⼒强的测试,能在失败的时候给你⾮常迅速的反馈,看到测试时,你就知道它测的业务点是啥 测试挂掉时,能清楚地知道失败的业务场景、期望数据与实际输出的差异,跟写声明式的代码⼀样的道理,测试需要都是简单的声明:准备数据、调⽤函数、断⾔,让⼈⼀眼就明⽩这个测试在测什么。如果含有逻辑,你读的时候就要多花时间理解;⼀旦测试挂掉,你咋知道是实现挂了还是测试本⾝就挂了呢?
运⾏速度快
可以使⽤mock适当隔离掉三⽅的依赖,将依赖、集成等耗时、依赖三⽅返回的地⽅放到更⾼层级的测试中,有策略性地去做 隔离性
单元测试是对代码独⽴的单元进⾏测试,这个独⽴的意思不是说这个函数(单元)不会调⽤另外⼀个函数(单元),⽽是说我们在测试这个函数的时候如果它有调⽤到其它的函数我们就需要mock它们,从⽽将我们的测试逻辑只放在被测试函数的逻辑上,不会受到其它依赖函数的影响
最后我们带⼊项⽬实际开发⼀下吧
(六)react单元测试框架enzyme实际应⽤
1. 测试⼯具:主要⽤到的测试⼯具是 jest 和 enzyme
2. 待测组件:可以添加删除的⼀个简单列表;
3. 我们想要测试四点:
1、组件渲染
2、渲染时初始待办事项的展⽰
3、我们可以创建⼀个新的待办事项然后返回三个待办事项
4、我们可以删除⼀个初始的待办事项并且只留下⼀个
上代码ing…
上组件
import React,{ useState, useRef }from"react";
const Todo=()=>{
const[list, setList]=useState([
{ id:1, item:"Fix bugs"},
{ id:2, item:"Take out the trash"}
]);
const todoRef =useRef();
const removeTodo= id =>{
setList(list.filter(todo => todo.id !== id));
};
const addList= data =>{
let id = list.length +1;
setList([
...list,
{
id,
item: data
}
]);
};
const handleNewTodo= e =>{
e.preventDefault();
const item = todoRef.current;
addList(item.value);
item.value ="";
};
return(
<div className="container">
<div className="row">
<div className="col-md-6">
<h2>Add Todo</h2>
</div>
</div>
<form>
<div className="row">
<div className="col-md-6">
<input
type="text"
autoFocus
ref={todoRef}
placeholder="Enter a task"
className="form-control"
data-testid="input"
/>
</div>
</div>
<div className="row">
<div className="col-md-6">
<button
type="submit"
java技术介绍百度百科onClick={handleNewTodo}
className="btn btn-primary"
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论