Java-Junit单元测试

1、什么是单元测试

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的面向对象时代,单元测试所说的单元是指类。以我的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。单元测试不应过分强调面向对象,因为局部代码依然是结构化的。

其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,把这种单元测试称为临时单元测试。只进行了临时单元测试的软件,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。

我们编写代码时,一定会反复调试保证它能够编译通过。如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。但代码通过编译,只是说明了它的语法正确;我们却无法保证它的语义也一定正确,没有任何人可以轻易承诺这段代码的行为一定是正确的。幸运的是,单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。

什么时候测试?单元测试越早越好,早到什么程度?极限编程(Extreme Programming,或简称XP)讲究TDD,即测试驱动开发,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从经验来看,先编写产品函数的框架,然后编写测试函数,针对产品函数的功能编写测试用例,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试用例。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的直接返回一个合适值,编译通过后再编写测试代码,这时,函数名、参数表、返回类型都应该确定下来,所编写的测试代码以后需修改的可能性比较小。

由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核。

软件要进行进一步的集成测试,必须基于完成单元测试已经完成的基础上。不能指望等集成测试的时候再找出系统的所有Bug。因为规模越大的代码集成意味着复杂性就越高。如果软件的单元没有事先进行测试,开发人员很可能会花费大量的时间仅仅是为了使软件能够运行,而任何实际的测试方案都无法执行。

单元测试命名规范:(a) 类放在test包中;(b) 类名用XXXTest结尾;(c) 方法用testMethod命名。

2、什么是集成测试

集成测试,也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试。实践表明,一些模块虽然能够单独地工作,但并不能保证连接起来也能正常的工作。一些局部反映不出来的问题,在全局上很可能暴露出来。

集成测试是在单元测试的基础上,测试在将所有的软件单元按照概要设计规格说明的要求组装成模块、子系统或系统的过程中各部分工作是否达到或实现相应技术指标及要求的活动。也就是说,在集成测试之前,单元测试应该已经完成,集成测试中所使用的对象应该是已经经过单元测试的软件单元。这一点很重要,因为如果不经过单元测试,那么集成测试的效果将会受到很大影响,并且会大幅增加软件单元代码纠错的代价。

3、什么是系统测试

系统测试,英文是System Testing。是对整个系统的测试,将硬件、软件、操作人员看作一个整体,检验它是否有不符合系统说明书的地方。这种测试可以发现系统分析和设计中的错误。如安全测试是测试安全措施是否完善,能不能保证系统不受非法侵入。再例如,压力测试是测试系统在正常数据量以及超负荷量(如多个用户同时存取) 等情况下是否还能正常地工作。

系统测试是将经过集成测试的软件,作为计算机系统的一个部分,与系统中其他部分结合起来,在实际运行环境下对计算机系统进行的一系列严格有效地测试,以发现软件潜在的问题,保证系统的正常运行。系统测试的目的是验证最终软件系统是否满足用户规定的需求。

主要内容包括:
(1)功能测试。即测试软件系统的功能是否正确,其依据是需求文档,如《产品需求规格说明书》。由于正确性是软件最重要的质量因素,所以功能测试必不可少。

4、Junit测试框架使用

JUnit是一个Java语言的单元测试框架。Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,可以用Junit进行自动测试。

4.1、Junit4.x版本

(1)使用junit4.x版本进行单元测试时,不用测试类继承TestCase父类,因为,junit4.x全面引入了Annotation来执行我们编写的测试。
(2)junit4.x版本,引用了注解的方式,进行单元测试;
(3)junit4.x版本我们常用的注解:
这里写图片描述

4.2、使用Junit进行单元测试

在Eclipse中新建一个项目叫JUnitTest,编写一个Calculator类,这是一个能够实现加减乘除、平方、开方的计算器类,然后对这些功能进行单元测试。
这里写图片描述
生成JUnit测试框架:在Eclipse的Package Explorer中用右键点击该类弹出菜单,选择“New JUnit Test Case”。如下图所示:
这里写图片描述
这里写图片描述
点击“下一步”后,系统会自动列出你这个类中包含的方法,选择你要进行测试的方法。此例中,我们仅对“加、减、乘、除”四个方法进行测试。
这里写图片描述
之后系统会自动生成一个新类CalculatorTest,里面包含一些空的测试用例。你只需要将这些测试用例稍作修改即可使用。
完整的CalculatorTest代码如下:
这里写图片描述
这里写图片描述
这里写图片描述

进度条是红颜色表示发现错误,具体的测试结果在进度条上面有表示“共进行了4个测试,其中1个测试被忽略,一个测试失败”。

附:单独测试类中一个函数的方法如下:
这里写图片描述

4.3、限时测试

对于那些逻辑很复杂,循环嵌套比较深的程序,很有可能出现死循环,因此一定要采取一些预防措施。限时测试是一个很好的解决方案。我们给这些测试函数设定一个执行时间,超过了这个时间,他们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些Bug了。要实现这一功能,只需要给@Test标注加一个参数即可,代码如下:
这里写图片描述
这里写图片描述

4.4、测试异常

JAVA中的异常处理也是一个重点,因此你经常会编写一些需要抛出异常的函数。那么,如果你觉得一个函数应该抛出异常,但是它没抛出,这算不算Bug呢?这当然是Bug,并JUnit也考虑到了这一点,来帮助我们找到这种Bug。例如,我们写的计算器类有除法功能,如果除数是一个0,那么必然要抛出“除0异常”。因此,我们很有必要对这些进行测试。代码如下:
这里写图片描述
如上述代码所示,我们需要使用@Test标注的expected属性,将我们要检验的异常传递给他,这样JUnit框架就能自动帮我们检测是否抛出了我们指定的异常。

4.5、参数化测试

我们可能遇到过这样的函数,它的参数有许多特殊值,或者说他的参数分为很多个区域。例如,测试一下“计算一个数的平方”这个函数,暂且分三类:正数、0、负数。在编写测试的时候,至少要写3个测试,把这3种情况都包含了,这确实是一件很麻烦的事情。测试代码如下:
这里写图片描述
为了简化类似的测试,JUnit4提出了“参数化测试”的概念,只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。代码如下:
这里写图片描述
这里写图片描述
代码分析如下:
为这种测试专门生成一个新的类,而不能与其他测试共用同一个类,此例中我们定义了一个SquareTest类。

为这个类指定一个Runner,而不能使用默认的Runner,@RunWith(Parameterized.class)这条语句就是为这个类指定了一个ParameterizedRunner。

定义一个待测试的类,并且定义两个变量,一个用于存放参数,一个用于存放期待的结果。定义测试数据的集合,也就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰。

4.6、精度测试

对于浮点数的断言测试,需要添加精度才行。

// pass
Assert.assertEquals(6.1, 6.0, 0.0);
// error
Assert.assertEquals(6.01, 6.0, 0.000);

比如:
这里写图片描述
添加delta精度后:
这里写图片描述


5、借助Mock工具进行测试

5.1、使用情景

在现实的软件开发过程中,我们经常需要协同其他同事一起来完成某个模块的功能开发,或者需要第三方资源,比如您需要一个短信网关,您需要一个数据库,您需要一个消息中间件,当您进行测试依赖于这些资源的代码时候,不可能在每次都具备相应的环境,这将是一个很不现实的问题,如果当您依赖于其他模块而无法进行单元测试的时候,此时该模块的质量风险就有两个,第一是您所负责的代码,第二是您所依赖的代码,您所依赖您没有办法在很快的时间协调到资源, 那么您所负责的代码由于不具备单元测试环境没有办法进行测试,很可能存在极大的风险,因此如何测试您的代码,让他的质量达到百分之百的可用, 这就是 Mock 存在的必要。我们如何在没有数据库的时候能够测试我们的 Service,进行支付的测试等等,这才是 Mock 要解决的问题PowerMock(PowerMock 也是一种 Mock,但是他主要是解决其他 Mock 不能解决的问题)。

在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,远程服务, 文件系统等等)。 而我们没法控制这些外部依赖的对象,为了解决这个问题,我们就需要用到Mock工具来模拟这些外部依赖的对象,来完成单元测试。

在软件测试领域,Mock的意思是模拟,简单来说,就是通过某种技术手段模拟测试对象的行为,返回预先设计的结果。这里的关键词是预先设计,也就是说对于任意被测试的对象,可以根据具体测试场景的需要,返回特定的结果。

Mock可以用来解除测试对象对外部服务的依赖(比如数据库,第三方接口等),使得测试用例可以独立运行。

5.2、应用实例

hello word 的使用!获取所有员工的个数。我们现在有一个 Service 类,就是 EmployeeService,其中有一个方法需要获取数据库中雇员的数量,Service 代码如下所示:
这里写图片描述
可以看到,创建Service的时候需要传递一个EmployeeDao这个类,也就是说 Service依赖Persistence,如果想要测试Service 就需要完全看 Persistence 的脸色,我们再来看看 Persistence 代码,如下所示:
这里写图片描述
哇!你死定了,你肯定调用不了 Dao,无法正常完成 Service 的测试,我为什么要在Persistence的方法抛出UnsupportedOperationException呢?目的就是告诉大家该方法可能由于某种原因(没有完成,或者资源不存在等)无法为 Service 服务,难道你不需要测试 EmployeeService 么?肯定要测试,那么我们就硬着头皮来写测试用例吧:
这里写图片描述
比较的遗憾,这个肯定的会失败的哦!不会成功的,我们的Dao层的数据都还没有实现怎么可能会有好的数据返回呢?所以啊,这个时候我们就模拟啊~,当我们的service层调用代码的时候,我们就可以使用啦!employeeDao.getTotal();我们要去模拟这个方法的返回值,必须的去请求我们的返回的数据,这个时候就是模拟啦!

请大家忘记此时此刻我抛出来的异常,幻想成此时此刻数据库连接不上,问题现在很明显,数据库连接不通,我们无法测试 Service,难道真的就无计可施了么?好吧,有请我们的主角 PowerMock 闪亮登场,请看下面的测试用例:
这里写图片描述
当你再次运行时你会发现此时此刻运行通过, 编写一下上述的代码我们先来有个简单的认识,所谓 Mock 就是创建一个假的,Mock 那个对象就会创建一个假的该对象,此时该对象是一单纯的白纸,需要你对其进行涂鸦,第二句话 when…then 语法就是您的涂鸦, 您期望调用某个方法的时候返回某个您指定的值。完全让 EmployeeDao 根据你的意愿来运行,所以想怎样测试 EmployeeService 就怎样测试。

5.3、应用实例-扩展

创建员工,返回值为void的测试!我们再来增加另外一个需求,就是创建一个 Employee,这就意味着我们需要分别在Service 和 Dao 中增加相应的两个接口:
这里写图片描述
因为此时“数据库资源不存在” ,相信大家一定很清楚这一点,但是这不是本小节中所要讲述的重点,重点在于 addEmployee 方法是一个 void类型的,也就是我们没有办法断言想要的结果是否一致,而 mock 厚的 addEmployee 方法事实上是什么都不会做的,此时我们该如何进行测试呢? 比如log记录日志…里面含有httpsession对象,我们也是可以不需要打印日志的,直接不管他啦!但是测试的时候我们不能把他直接注释掉啊!忘记了怎么办呢?这样的方式很不对!以前我测试的时候也是这么直接的处理的,实习呢之后的今天,看到了这个非常的好奇哦,还不错,学到了好东西!测试非常的有必要,比编写代码更重要。反正我写测试比编写代码的时间多!

简单思考一下我们其实只是想要知道 addEmployee 方法是否被调用过即可, 当然我们可以假设他 add 成功或者失败,这就根据您的 test case 来设定了,好了,有了这个概念之后我们来看看如何测试 void 方法,其实就是 mock 中一个很重要的概念 Verifying:
这里写图片描述
然后用 junit 运行肯定能够通过, 其中 Mockito.verify 主要用来校验被 mock 出来的对象中的某个方法是否被调用,我们的 PowerMock helloworld 也到此结束了。


猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/81879437