课件

3.1 单元测试概述.pdf

现实的开发问题

image.png
在现实开发中,缺乏软件工程经验的开发人员往往在编码完成之后,就迫不及待地进行软件集成工作,表面上看这样做可以很快地看到实际系统,但是由于缺少应有的单元测试,系统中经常充满了各种Bug,也会造成故障难以定位、修复困难等问题,这样就会增加额外的时间和成本。

image.png
上图展示了编码和测试的不同阶段,引入和发现缺陷的情况,以及修复它们的成本,开发人员在编写代码的同时不可避免地会引入缺陷,图中的数据告诉我们,大多数的缺陷是在编码的初期产生的,修复它们的成本在初期也比较低,越往后拖增长的幅度就越大,单元测试是软件开发过程中重要的质量保证活动,它的质量在很大程度上影响着软件产品的最终质量。

单元测试概述

image.png
单元是构造系统的基础,这就好比是长城的城墙,如果每块砖的质量不过关,那么建造的城墙就不会坚固,编写代码也是如此,底层代码出现问题就会牵扯到上层代码,它的修改也可能会导致其他代码的一连串改动,从而影响整个产品的质量,只有打造坚实的基石才能保证上层建筑的坚固。

image.png
单元测试(Unit Testing)是对软件中的最小可测试单元进行检查和验证,它要测试和验证程序代码中每一项功能的正确性,为后续的开发提供支持。编写单元测试可以使开发人员从调用者的角度进行观察和思考,这样编写的程序更易于调用和测试,也可以将程序的缺陷降低到最小。单元测试也是一种文档化的行为,测试用例可以说明函数或者类是如何使用的。自动化的单元测试也有助于进行回归测试。

image.png
我们在前面讲过产品的质量是在构建的过程中形成的,开发人员必须对自己的代码负责,单元测试就是对所开发代码质量的一个基本的承诺。对于程序员来说,编程不仅仅意味着编写代码还要完成单元测试,在现实中我们发现那些代码质量最好、开发速度最快的程序员,也是单位测试做得最好的程序员。

单元测试内容

单元测试的内容主要包括以下方面:
image.png

  • 模块接口:主要是检查参数表调用子模块的参数、全程数据、文件IO等内容;
  • 局部数据结构:主要检查数据类型说明、初始化、缺省值等方面的问题,还要查清全程数据对模块的影响;
  • 边界条件:要特别注意数据流或者控制流中刚好等于、大于或者小于边界值的情况,因为这些地方非常容易出错;
  • 独立路径:主要是对模块中重要的执行路径进行测试,通过对基本执行路径和循环进行测试,可以发现大量的路径错误;
  • 出错处理:是检查模块的错误处理功能是否存在错误或者缺陷,如果对模块运行时间有要求的话,还要专门进行关键路径测试,以确定最坏情况下和平均意义下影响模块运行时间的因素;

通过单元测试我们可以保证单元模块的质量,这样为整个的系统打造了坚实的基础。

单元测试原则

单元测试包括快速、独立、可重复、自我验证和及时五个原则:
image.png

  • 单元测试必须要快,太慢的话就没有人再愿意频繁地运行它。
  • 单元测试也要相互独立,否则一个测试没有通过就会导致一连串的失败,很难定位问题。
  • 单元测试是要能够重复执行的,而且结果也是可以重现的。
  • 单元测试执行结束之后,应该由布尔输出来显示是否通过,而不应该通过人工的方式来确认。
  • 开发人员应该及时编写单元测试代码,最好是在开发单元代码之前来完成。

单元测试过程

单元测试过程主要包括以下步骤:
image.png

  • 首先我们要找出程序中潜在的最大问题区,来确定哪些部分需要做单元测试。
  • 其次针对要做的测试来编写相应的测试用例。
  • 之后编写单元测试代码、执行单元测试,产生测试结果。
  • 如果测试结果满足了测试质量的要求,那么整个测试结束。
  • 否则如果不满足要求,还要根据结果和测试的要求,来修改和增加新的测试用例,再进一步地进行测试。

单元测试质量

衡量测试质量的指标一般有测试通过率和测试覆盖率:
image.png
测试通过率是在测试过程中测试用例的通过比例,单元测试一般都是要达到100%。

测试覆盖率是用来衡量测试的完整性,它可以告诉我们测试是否充分,以及测试的弱点在哪里,我们可以通过增加测试用例来提高覆盖率,进而提升测试质量。

单元测试一般用代码覆盖率来衡量,代码覆盖率大致达到70%到80%就可以了,过高的覆盖率可能会造成测试成本的大大增加。

代码覆盖率的度量有多种不同的方式:
image.png
我们将在自盒测试方法一讲中进行详细地介绍。

单元测试方法

单元测试包括静态测试和动态测试两种类型:
image.png
其中静态测试是通过人工分析或者程序正确性证明的方式,来确认程序的正确性,而动态测试则是通过动态的分析和运行程序的方式,来检查和确认程序中是否存在问题。

对于测试用例设计来说,有黑盒测试和白盒测试两种技术。
image.png
黑盒测试(Black Box Testing)也称功能测试,它是把测试对象看成是一个黑盒子,只是根据需求规格说明来设计有代表性的测试输入,再通过测试执行的输出结果,来检查程序的功能是不是符合规格说明。

image.png
自盒测试(White Box Testing)也称结构测试,它允许测试人员根据程序内部的结构,来设计测试用例,对于程序的逻辑路径等进行测试。

一个单元模块并不是独立存在的,模块之间总会存在相互调用的关系:
image.png
比如说一个被测模块,通常会被上层的模块来调用,同时它又调用多个下层的模块,那么在单元测试的时候,如何来保证被测模块是独立的,并且能够构成一个可运行的程序呢?

在这种情况下,我们需要开发驱动模块和桩模块,他们将被测模块进行隔离,并帮助完成单元测试:
image.png
其中驱动模块用于替代上层的调用模块,它去调用被测模块,并且判断被测模块的返回值是否与测试用例的预期结果相符。桩模块则用于替代下层的调用模块,它要模拟地返回所替代模块的各种可能的返回值。

单元测试之xUnit

image.png
单元测试一般需要借助工具来编写测试代码,目前最流行的是针对不同编程语言的系列Unit框架,我们在后面单元测试工具这一讲中将介绍Python语言的PyUnit,其他的框架大家可以在以后的开发中自己学习和使用。

image.png
xUnit测试一般适合单个函数或类的测试,尤其是纯函数或者接口级别的测试,对于一些复杂场景就很难适用,但是这些场景往往才是测试的重点,也是难点所在,以分布式系统为例,类和函数级别的单元测试是远远不够的,这种系统主要是测试进程之间的交互,例如一个进程收到客户请求应该如何处理,然后转发给其他进程等。另外分布式系统的测试通常更关注一些异常的路径,那么这种复杂的场景应该如何进行单元测试呢?

单元测试之Mock

Mock方法可以帮助我们解决前面所说的那些复杂场景中遇到的难题。
image.png
这种测试是在测试过程中对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象,也就是Mock对象来创建以便进行测试,上图列出了一些Mock测试适合的场景。

举例说明Mock测试的基本方法:
image.png
春节期间很多同学可能都玩过接龙红包,它是通过猜金额这样一个小游戏的方式来实现朋友之间的互动,并且可以领取春节红包,在这个程序中,GiftMoney这个类允许设定红包的金额和上下限,并且将所猜的金额和设定金额进行比较,由于数据是存放在数据库中,所以这一个类就需要调用数据库存取访问的方法,那么这种情况下应该如何进行测试?

image.png
刚才红包的问题可以抽象成ClaassA对ClassB的访问,在进行Mock测试时需要把程序之间的直接调用,转换成通过接口进行访问,也就是说先抽象出一个接口InterfaceB,ClassA直接调用它,ClassB来具体地实现它,然后在使用Mock对象来模拟所调用的对象及其行为,这时被测模块并不知道它所引用的究竟是真实对象还是Mock对象,通过这样的方式可以把被测模块和所依赖的模块进行隔离,从而实现复杂场景下的单元测试。

image.png
我们再来思考一个微信抢票应用的例子,开发人员编写的抢票程序是通过直接调用系统函数来获得当前的系统时间,在单元测试的时候我们需要设定不同的时间进行抢票测试,如果我们要利用真实的系统时间来测试的话,就只能苦苦等到实际设定的时间,显然这是一个很笨的做法,那么如果使用Mock的方法进行测试,应该如何来重构这个程序呢?对于这个问题请大家结合刚才讲解的Mock测试内容,在课下进行思考和讨论。