单元测试

什么是单元测试:

  • 单元是软件中承担单一责任的单位,一个函数、一个文件、一个类、一个模块都可以称为一个单元。
  • 单元测试便是对软件设计的最小单位进行正确性测试,以检验程序单元是否满足功能,性能,接口,设计规范等。
  • 单元测试本质上也是代码

为什么现在没写单测:

  • 当下的收益不高[1]
  • 相比后端接口的单测,前端单测相对复杂
  • 前端面向UI编程,UI变动大 (拆的够细,减少UI的单测)
  • 没有写单测的习惯,增加了工作量,没有写纯函数的意识,不利于测试
  • 单测的工具相对难学又难用

为什么要写单元测试:

  • 没有人敢保证“我”的代码没问题
  • 方便后期需要做重构,改动。保证改动的同时不影响其他功能
  • 人肉测试无法完全覆盖
  • 快速发现错误,减少调试的时间
  • 单元测试本身就是一个无价的文档记录
    现代企业数字化竞争日益激烈,业务端快速上线、快速验证、快速验证失败,对技术端提出了更高的要求:更快上线、持续上线。怎么样衡量这个“更快”呢?不写单元测试、不写好的单元测试,你就快不起来。为啥呢?因为每次发布,你都要投入人力来进行手工测试;因为没有自动化测试,就不敢随意重构,这又导致代码逐渐腐化,使得你的开发速度降低。

应用会变大,需求一定会增加,直至再也没有一个人能够了解应用的所有功能,那时对应用做出修改的成本将变得很高。因此,意图依赖人、依赖手工的方式是低效的,从时间维度上来讲也是不可能的。因此,为了能随时重构整理代码,这就需要我们有一套自动化的测试套件,它能帮我们提供快速反馈,做质量的守卫者。只有解决了人工、质量的这一环,开发效率才能稳步提升,团队和企业的高响应力才可能达到。

在”快速响应“随时重构”的基础来谈要不要单元测试,我们就可以很有根据了,而不是含糊不清地回答“看项目的具体情况”了。显然,写出易于理解、易于修改、可以重构的代码,是每个开发者的本来职责,而单元测试正是达成此一目的的唯一途径。

测试种类多种多样,为什么我要重点谈单元测试呢?因为这篇文章主题就是谈单元测试啊…它写起来相对最容易、运行速度最快、反馈效果又最直接

什么是好的单元测试

开始之前,我们先来看个例子,即一个最简单的JavaScript单元测试长什么样:

// production code
const computeTotalAmount = (products) => {
  return products.reduce((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 - Prepare data
  const products = [
    { name: 'nike', price: 200 },
    { name: 'adidas', price: 300 },
    { name: 'lining', price: 500 },
  ]

  // when - Call the function under test
  const result = computeTotalAmount(products)

  // then - 断言结果
  expect(result).toBe(1000)
})

遵循这个given-when-then的结构,可以让你写出比较清晰的测试结构,既易于阅读,也易于编写。此外,编写容易维护的单元测试还有一些原则,这些原则对于任何语言、任何层级的测试都适用。

只关注输入输出,不关注内部实现(只要测试输入没有变,输出就不应该变。这个特性,是测试支撑重构的基础)
只测一条分支

表达力极强
测试描述。遵循上一条原则(一个单元测试只测一个分支)的情况下,描述通常能写出一个相当详细的业务场景。这为测试的读者提供了极佳的业务上下文

测试数据准备。无关的测试数据(比如对象中的很多无关字段)不应该写出来,应只准备能体现测试业务的最小数据

输出报告。选用断言工具时,应注意除了要提供测试结果,还要能准确提供“期望值”与“实际值”的差异

不包含逻辑
运行速度快

什么时候写单元测试:

  • 你写的 util,format,hooks等 被其他类调用
  • 你写的公共component,被其他功能调用
  • 你写的是否是一个开源项目,被人所引用

在这里插入图片描述

以下不用

实践

由简单的实例到进入到项目中
storybook放到项目中

覆盖率

代码覆盖率 = 代码的覆盖程度,一种度量方式

语句覆盖(Statement Coverage)

又称行覆盖(LineCoverage),段覆盖(SegmentCoverage),基本块覆盖(BasicBlockCoverage),这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了
语句覆盖常常被人指责为“最弱的覆盖”,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。假如你的上司只要求你达到语句覆盖,那么你可以省下很多功夫,但是,换来的确实测试效果的不明显,很难更多地发现代码中的问题。

function foo(a:number,b:string){
  return a/b
}

test case: a = 10,b = 5;
这个测试结果会告诉我们代码覆盖率达到了100%,并且所有测试案例都通过了。,然而当我们让 b = 0的时候,会抛出一个0异常

判定覆盖 它度量程序中每一个判定的分支是否都被测试到了。
条件覆盖 它度量判定中的每个子表达式结果true和false是否被测试到了。条件覆盖针对判断语句里面案例的取值都要去一次,不考虑条件的取值。
function foo(a:number,b:number){
  if(a < 10 || b < 10){ // 判定
    return 0  // 分支1
  }else{
    return 1 // 分支2
  }
}

设计判定覆盖案例时,我们只需要考虑判定结果为true和false两种情况,因此,我们设计如下的案例就能达到判定覆盖率100%:

TestCaes1: a = 5, b = 任意数字  覆盖了分支一
TestCaes2: a = 15, b = 15          覆盖了分支二

设计条件覆盖案例时,我们需要考虑判定中的每个条件表达式结果,为了覆盖率达到100%,我们设计了如下的案例:

TestCase1: a = 5, b = 5       true,  true
TestCase4: a = 15, b = 15   false, false

需要特别注意的是:条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。因此,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。比如上面的例子,假如我设计的案例为

TestCase1: a = 5, b = 15  true,  false   分支一
TestCase1: a = 15, b = 5  false, true    分支一

我们看到,虽然我们完整的做到了条件覆盖,但是我们却没有做到完整的判定覆盖

路径覆盖 它度量了是否函数的每一个分支都被执行
function foo(a:number,b:number){
  ler result = 0;
  if(a < 10){ // 判定
    result += 1  // 分支1
  }
  if(b < 10){
    result += 10 // 分支2
  }
  return result
}

语句覆盖:

TestCase a = 5, b = 5   nReturn = 11 //语句覆盖率100%

判定覆盖:

TestCase1 a = 5,   b = 5     nReturn = 11
TestCase2 a = 15, b = 15   nReturn = 0
//判定覆盖率100%

提交覆盖:

TestCase1 a = 5,   b = 15   nReturn = 1
TestCase2 a = 15, b = 5     nReturn = 10
//条件覆盖率100%

路径覆盖:

TestCase1 a = 5,    b = 5     nReturn = 0
TestCase2 a = 15,  b = 5     nReturn = 1
TestCase3 a = 5,    b = 15   nReturn = 10
TestCase4 a = 15,  b = 15   nReturn = 11
//路径覆盖率100%

可以看到路径覆盖将所有可能的返回值都测试到了。

还有一些其他的覆盖方式,如:循环覆盖(LoopCoverage),它度量是否对循环体执行了零次,一次和多余一次循环。
所以覆盖率数据只能代表你测试过哪些代码,不能完全代表你是否测试好这些代码,测试人员不能盲目追求代码覆盖率,而应该想办法设计更多更好的案例,哪怕多设计出来的案例对覆盖率一点影响也没有。

FIRST

  • F(Fast):即测试执行要迅速,如果执行很慢,那就说明我们的代码有问题。
  • I(Independent):每个测试之间要独立,即每个测试要做到能单独运行,不要相互设置先决条件,否则就会出现一个测试有问题,其他测试都出现错误。
  • R(Repeatable):每个测试都要做到可重复执行,不管在任何环境下。
  • S(Self-Validating):即测试应该有结果输出,应该有一个判断,是否和预期相符合,不能最后还要人工去判断。
  • T(Timely):即测试要是及时的,包含两方面,一是测试应该在生产代码前编写,二是,需求或生产变动,测试也要随之变动,保证测试代码是最新的。

成本曲线

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值