轻松入门:Junit在Java单元测试中的详细指南
单元测试机制及术语
软件开发V形模式
说到单元测试,就不得不提到软件工程中的软件开发模式-V形模式。V模型是对瀑布模型的修正,强调了验证活动,由Paul Rook在1980年率先提出。在瀑布模型中,由于早期的错误可能要等到开发后期的测试阶段才能发现,所以可能带来严重的后果。V模型就是在这点上改进了瀑布模型,即在软件开发的生存期中,开发活动和测试活动几乎同时开始,这两个并行的动态的过程就会极大地减小bug和error出现的概率。V模型是瀑布模型的变种,它反映了测试活动与分析和设计的关系。
所以开发在代码编写过程中,单元测试就是不可缺少的一环。然而在现在各个开发团队都采用敏捷开发的模式中,100%代码和分支覆盖率的单元测试需要付出的代价太大,不适合敏捷开发模式。所以在实际开发过程中会简化单测要求,一般只要求代码和分支覆盖率在80%左右。
单元测试机制
- 测试按照阶段分为单元测试,结合测试,系统测试
- 单元测试只测试标的对象
- 通过打桩,模拟标的对象的所有对象引用及方法调用,实现标的对象的代码和分支覆盖
- 单元测试只考虑测试对象本身逻辑,其他引用对象或调用方法的逻辑都不在考虑范围内
单元测试机制示意图。
单元测试术语
- 模拟:模拟测试对象引用其他对象
- 打桩:当调用模拟的对象中方法时,返回指定的值
- 执行:执行测试对象的测试方法
- 断言:验证测试方法的返回值是否和预期一致
Junit引入
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
在测试方法上引入注解
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest
RunWith注解指明了单测使用什么方式启动。如果只是测单个类的逻辑,将其他类对象的引用全部打桩模拟返回,则可以使用MockitoJUnitRunner.class。这种方式启动迅速,效率高,但是无法使用Spring工程中的环境配置参数。
如果是希望单测启动后,依旧使用工程的各种配置信息,并且不只是测当前类方法,而是测这个类方法为入口的整个逻辑,则可以使用@RunWith(SpringRunner.class)作为启动方式。
单元测试常用注解
-
@BeforeClass:标注在方法上,该方法全局只会执行一次,而且是第一个运行。
当我们运行几个有关联的用例时,可能会在数据准备或其它前期准备中执行一些相同的命令,这个时候为了让代码更清晰,更少冗余,可以将公用的部分提取出来,放在一个方法里,并为这个方法注解@BeforeClass。意思是在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。必须是public static void -
@Before:标注在方法上,在测试方法执行前执行。
在每个用例运行之前都运行一次。主要用于一些独立于用例之间的准备工作。
比如两个用例都需要读取数据库里的用户A信息,但第一个用例会删除这个用户A,而第二个用例需要修改用户A。那么可以用@BeforeClass创建数据库连接。用@Before来插入一条用户A信息。
须是public void,不能为static。不止运行一次,根据用例数而定。 -
@Test:标注测试方法。标记测试方法,Test类都必须继承一个基类。
注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。 - @After:在测试方法运行之后运行。用法和Before相对
-
@AfterClass:标注在方法上,该方法全局只会执行一次,而且是最后一个运行。
跟@BeforeClass对应,在测试类里所有用例运行之后,运行一次。用于处理一些测试后续工作,例如清理数据,恢复现场。
必须是public static void,即公开、静态、无返回。这个方法只会运行一次。 -
@Ignore:和@Test相对应。被Ignore标记的方法,不会被执行。
有时候我们想暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。在运行结果中,junit会统计忽略的用例数,来提醒你。
下面都是基于第一种,对单个类对象的单元测试为例。
常规方法测试
首先对测试对象类中所有注入对对象进行打桩。
假设我的测试对象类只引用了一个TestA对象
public class AppCommonServiceImplTest extends HermesApplicationTests {
// 打桩测试对象的注入对象
@Mock
private TestA testA;
// 注入测试对象
@InjectMocks
private TestServiceImpl testServiceImpl;
/**
* 测试方法,一般使用要测试的方法名后面加Test来组成测试方法名
*/
@Test
@SuppressWarnings("unchecked")
public void queryAddTruckTypeListTest(){
// 模拟一个返回参数
List<Person> retList = new ArrayList<>();
Person testVo = new SysDictionary();
testVo.setAge(23);
testVo.setSex("1");
testVo.setName("测试");
retList.add(testVo);
// 打桩测试方法调用的外部方法。
// 语义是,当打桩的实例sysDictionaryMapper的selectList方法
// 以任何LambdaQueryWrapper实例为入参调用时,返回模拟的方法
Mockito.when(sysDictionaryMapper.selectList(Mockito.any(LambdaQueryWrapper.class))).thenReturn(retList);
ResultVo resultVo = testServiceImpl.testMethod();
// 打印返回结果
LOGGER.info("queryAddServiceTypeListTest result: {}", JsonUtils.obj2json(resultVo));
// 断言,即验证返回结果是否正确。
// 例如我假设我的测试方法是把查询到的人,将年龄拼接在名字后面返回
// 验证结果是否正确
Assert.assert(resultVo.getName(),"测试(23)");
}
以上就是一个简单的单元测试方法。省略了测试对象和测试方法的代码部分。
特殊类及方法测试
PowerMockito打桩静态方法
假设测试的对象方法中含有静态工具类的使用,这个时候,就需要用到PowerMockito来对静态方法进行打桩。
然后和常规方法一样的方式,对静态引用进行打桩。
// 此处打桩,当date工具类调用format转换日期格式时,抛出运行时异常
PowerMockito.when(DateUtils.format(Mockito.any(Date.class)))
.thenThrow(new RuntimeException);
打桩没有返回值的方法(void方法)
使用Mockito的doNothing和doThrow方法
Mockito.doNothing().when(mock).method(args); //不做任何处理
Mockito.doThrow(toBeThrown).when(mock).method(args); //抛错
例如
Mockito.doNothing().when(userServiceImpl)
.updateUserInfo(Mockito.any(User.class));
关于测试覆盖率
运行测试方法
点击方法前面的小图标,就可以执行测试方法
- Run:直接运行
- Debug:使用调式方式运行
- Run ‘method()’ with Coverage:运行完测试用例后计算覆盖率
查看测试覆盖率
方法执行完后,可以在coverage窗口查看代码覆盖率。
如果没有自动加载出覆盖率,则需要自己制定计算覆盖率的范围。
如下图调出配置页面,或者直接点击上图中的Edit链接
在配置中,添加测试对象作为覆盖率计算的范围。
然后重新运行测试用例
三个指标分别时class覆盖率,方法覆盖率,行覆盖率。
因为计算范围只有一个类,覆盖了一个类,所以类覆盖率时100%。18个方法覆盖了8个,130行代码覆盖了40行。
查看测试覆盖情况
打开测试对象类,可以通过行号上的颜色来快速定位到已经覆盖到的行和未覆盖的行。
- 绿色部分为已覆盖代码
- 红色部分为未覆盖代码
- 左侧红色棱形为未覆盖分支
- 黄色棱形为部分覆盖分支
- 绿色棱形为全覆盖分支
常见问题分析
关于多次返回的方法打桩
Mockito.when(mockitoClass.method(Mockito.anyString()))
.thenReturn(aList).thenReturn(bList).thenReturn(null)
同一个打桩方法下,可以添加多个thenReturn,按照次序在循环调用中依次返回指定值。若调用次数大于thenReturn的指定次数,则超出的都返回最后指定的值。
带参数的Test注解
①设置超时时间,避免死循环不能终止
@Test(timeout=100)
public void infinity() { while(true); }
②声明报错
@Test(expected = Exception.class)
public void testDivide() throws Exception { cal.divide(1, 0); }
执行测试方法报错
执行方法时,报如下错误
解决方法如下,勾选argline复选框
以上就是博主整理的关于单测的一些问题,如果有不解或者不正之处,还望指出。
上一篇: Java单元测试学习心得分享
推荐阅读
-
轻松入门:Junit在Java单元测试中的详细指南
-
实战指南:轻松掌握Java IDEA和JUnit的单元测试
-
轻松入门:JUnit 5在Java单元测试中的应用
-
轻松入门:SpringBoot+Junit4的单元测试指南(上篇)
-
轻松入门:Python在AutoCAD中的二次开发指南(强烈推荐保存)
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾
-
Maven中的JUnit单元测试框架:入门指南
-
玩转Kotlin性能测试:JMH入门指南一 - 测试基础" "深入理解JMH在Kotlin中的应用:基准测试实战解析" "轻松实践Kotlin基准测试:JMH工具详解与实例总结
-
入门指南:理解ASCII编码在Java中的应用
-
在Java中轻松添加和读取Excel中的公式操作指南