概述

由于最近工作需要, 在项目中要做单元测试, 以达到指定的测试用例覆盖率指标。项目中我们引入的powermockito来编写测试用例, JaCoCo来监控单元测试覆盖率。关于框架的选择, 网上讨论mockito和powermockito孰优孰劣的文章众多, 这里就不多做阐述, 读者如有兴趣可自行了解。

依赖引入

<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule-agent</artifactId>
<version>1.6.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.6</version>
</dependency>

被测试类

public class PowerMockitoDemo {

    @Autowired
private StudentDao studentDao; @Autowired
private TeacherService teacherService; public void study() {
//doSomething
} private void play(Map<String, Object> project, Person person, int hours) {
//doSomething
} private boolean updateStudentName(String newName) {
//doSomething
} public ServiceResult grantRights(List<String> usernames, String rights, String orderid, String id) {
String value1 = PropertiesUtil.get("key1");
String value2 = PropertiesUtil.get("key2");
studentDao.saveRecord(usernames);//返回值类型void
Student student = studentDao.getStudentById(id);
boolean result = this.verifyParams(usernames);
teacherService.syncDB2Redis(usernames, orderid);
this.updateOperation(rights);
} private boolean verifyParams(List<String> usernames) {
//doSomething
} private void updateOperation(String rights) {
//doSomething
}
}

测试用例基类

//@PrepareForTest注解和@RunWith注解需结合使用,单独使用将不起作用
@RunWith(PowerMockRunner.class)
@PrepareForTest({RedisUtils.class})
@SuppressStaticInitializationFor({"com.test.util.RedisUtils", "com.test.util.HttpUtils"})//用于阻止类中的静态代码块执行
public abstract class BaseTest { public RedisUtils redisUtils; @Rule
public ExpectedException thrown = ExpectedException.none();//断言要抛出的异常 public void setUp() {
initMocks(this); PowerMockito.suppress(PowerMockito.constructor(ShardedJedisClientImpl.class, String.class));
redisUtils = PowerMockito.mock(RedisUtils.class);
Whitebox.setInternalState(RedisUtils.class, "redisUtil", redisUtils);//给类或实例对象的成员变量设置模拟值,这里是给RedisUtils类中的字段redisUtil设置模拟值 PowerMockito.suppress(PowerMockito.constructor(HttpUtils.class));
PowerMockito.mockStatic(HttpUtils.class);//mock类中所有静态方法
} /**
* @param instance 真实对象
* @param methodName 方法名
* @param args 形参列表
*/
public Object callPrivateMethod(Object instance, String methodName, Object... args) throws Exception {
return Whitebox.invokeMethod(instance, methodName, args);//调用私有方法
} }

测试用例

@PrepareForTest({PowerMockitoDemo.class, PropertiesUtil.class})//此处PowerMockitoDemo被测试类添加到@PrepareForTest注解中, 用于mock其静态、final修饰及私有方法;另外,PropertiesUtil工具类由于不通用,不适合抽取到基类BaseTest中, 可在子类mock
@SuppressStaticInitializationFor("com.test.util.PropertiesUtil")//用于阻止类中的静态代码块执行
public class PowerMockitoDemoTest extends BaseTest{ @org.powermock.core.classloader.annotations.Mock
private StudentDao studentDao; @org.powermock.core.classloader.annotations.Mock
private TeacherService teacherService; @org.powermock.core.classloader.annotations.Mock
@InjectMocks
private PowerMockitoDemo powerMockitoDemo; @Override
@Before
public void setUp() {
super.setUp();
PowerMockito.suppress(PowerMockito.constructor(PropertiesUtil.class));
PowerMockito.mockStatic(PropertiesUtil.class);//mock类中所有静态方法
} @Test
public void studyWhenCallSuccessfully() {
PowerMockito.doCallRealMethod().when(powerMockitoDemo).study();
//doSomething powerMockitoDemo.study();
} @Test
public void playWhenCallSuccessfully() {
Map<String, Object> project = new HashMap<String, Object>();
Person person = new Person();
int hours = 8; PowerMockito.doCallRealMethod().when(powerMockitoDemo, "play", Matchers.anyMapOf(String.class, Object.class), Matchers.any(Person.class), Matchers.anyInt());
//doSomething this.callPrivateMethod(powerMockitoDemo, "play", project, person, hours);
} @Test
public void updateStudentNameWhenCallSuccessfully() throws Exception {
String id = "9527"; PowerMockito.when(powerMockitoDemo, "updateStudentName", Matchers.anyString()).thenCallRealMethod();
//doSomething boolean actualResult = this.callPrivateMethod(powerMockitoDemo, "updateStudentName", id);
Assert.assertTrue(actualResult == true);
} @Test
public void getStudentByIdWhenCallSuccessfully() throws Exception {
List<String> usernames = new ArrayList<String>();
String rights = "万叶飞花流";
String orderid = "orderid";
String id = "id"; //调用真实方法
PowerMockito.when(powerMockitoDemo.grantRights(Matchers.anyListOf(String.class), Matchers.anyString(), Matchers.anyString(), Matchers.anyString())).thenCallRealMethod();
//当方法内重复调用同一个方法时, 可通过Matchers.eq()方法来指定实际入参来加以区分
PowerMockito.when(PropertiesUtil.get(Matchers.eq("key1"))).thenReturn("value1");
PowerMockito.when(PropertiesUtil.get(Matchers.eq("key2"))).thenReturn("value2");
//返回值类型为void,不做任何事情
PowerMockito.doNothing().when(studentDao).saveRecord(Matchers.anyListOf(String.class));
//类似需要调用数据库、redis、远程服务的,可直接模拟返回值,不做方法的真实调用
PowerMockito.when(studentDao.getStudentById(Matchers.anyString())).thenReturn(new Student());
//调用真实的私有方法
PowerMockito.when(powerMockitoDemo, "verifyParams", Matchers.anyListOf(String.class)).thenCallRealMethod();
//模拟私有方法返回值
PowerMockito.when(powerMockitoDemo, "verifyParams", Matchers.anyListOf(String.class)).thenReturn(true);
//模拟方法调用抛出异常。当被调用的方法头没有显式声明异常时, 则mock只支持unchecked exception,比如这里syncDB2Redis()方法签名没有声明任何异常,则thenThrow()模拟异常时只支持模拟运行时异常,使用非运行时异常将编译不通过
PowerMockito.when(teacherService.syncDB2Redis(Matchers.anyListOf(String.class), Matchers.anyString())).thenThrow(new RuntimeException("Failed to call remote service"));
PowerMockito.doNothing().when(powerMockitoDemo, "updateOperation", Matchers.anyString()); ServiceResult expectedResult = new ServiceResult();
ServiceResult actualResult = powerMockitoDemo.grantRights(usernames, rights, orderid, id);
//断言实际调用结果是否符合预期值
Assert.assertEquals(JSON.toJSONString(expectedResult), JSON.toJSONString(actualResult));
}
}

如上, 具体的阐释在代码注释中都已经标注, 抽取基类是为了提高代码可重用性。博主这里是为了演示, 所以代码看起来会有点臃肿, 在实际项目使用中, 可以通过静态引入 import static org.mockito.Mockito.when; 和 import static org.mockito.Matchers.*; 来简化代码, 提高可阅读性。

另外由于被测试类在测试方法中被mock掉, 且被@PrepareForTest注解标记时, JaCoCo工具统计测试覆盖率将忽略该测试类。可通过在基类BaseTest中添加 @Rule public ExpectedException thrown = ExpectedException.none(); , 并去掉 @RunWith(PowerMockRunner.class) 和 @SuppressStaticInitializationFor 来使统计测试覆盖率生效。但这里又有一个新问题产生, 基类修改之后, 发现测试用例无法进入debug调试, 因此建议先用修改前的基类来编写单元测试, 便于调试, 待测试用例完成后, 再修改基类令Jacoco统计覆盖率生效。

关于PowerMockito的实践, 博主目前在项目中的使用主要就涉及到了这些, 所以这次做了回标题党"XXX深入实践" ^_^, 后面如有接触新的相关知识点, 会陆续更新到本篇文章中。如有错误, 欢迎指正, 谢谢你^_^

参考资料

非web下的PowerMockito单元测试

powermockito单元测试之深入实践的更多相关文章

  1. [转载]单元测试之道(使用NUnit)

    首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而又忐忑的心情点击界面上的 ...

  2. 单元测试之道(使用NUnit)

    首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而 又忐忑的心情点击界面上 ...

  3. 补习系列(8)-springboot 单元测试之道

    目录 目标 一.About 单元测试 二.About Junit 三.SpringBoot-单元测试 项目依赖 测试样例 四.Mock测试 五.最后 目标 了解 单元测试的背景 了解如何 利用 spr ...

  4. ASP.NET Core搭建多层网站架构【3-xUnit单元测试之简单方法测试】

    2020/01/28, ASP.NET Core 3.1, VS2019, xUnit 2.4.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站架构[3-xUnit单元测试 ...

  5. iOS 单元测试之XCTest详解(一)

    iOS 单元测试之XCTest详解(一) http://blog.csdn.net/hello_hwc/article/details/46671053 原创blog,转载请注明出处 blog.csd ...

  6. 玩转单元测试之Testing Spring MVC Controllers

    玩转单元测试之 Testing Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html The Spri ...

  7. 玩转单元测试之WireMock -- Web服务模拟器

    玩转单元测试之WireMock -- Web服务模拟器 WireMock 是一个灵活的库用于 Web 服务测试,和其他测试工具不同的是,WireMock 创建一个实际的 HTTP服务器来运行你的 We ...

  8. 单元测试之NSNull 检测

    本文主要讲 单元测试之NSNull 检测,在现实开发中,我们最烦的往往就是服务端返回的数据中隐藏着NSNull的数据,一般我们的做法是通过[data isKindOfClass:[NSNull cla ...

  9. 使用VisualStudio进行单元测试之二

    借着工作忙的借口,偷了两天懒,今天继续单元测试之旅.前面说了如何进行一个最简单的单元测试,这次呢就跟大家一起来熟悉一下,在visual studio中如何进行数据驱动的单元测试. 开始之前先来明确一下 ...

随机推荐

  1. SSM(一)Mybatis基础

    1.持久化与ORM 持久化是数据在内存与硬盘间相互转化的过程 ORM即对象关系映射 程序员使用面向对象的思维方式处理数据,每个对象都是一个pojo.但是保存数据的时候,却以关系型数据库的方式存储.所以 ...

  2. 为什么现在这么多人开始学习Python?

    近几年Python编程语言在国内引起不小的轰动,有超越JAVA之势,本来在美国这个编程语言就是最火的,应用的非常非常的广泛,而Python的整体语言难度来讲又比JAVA简单的很多.尤其在运维的应用中非 ...

  3. H5 离线缓存的用法

    H5离线缓存基础系列   1.什么是离线缓存 离线缓存:离线缓存可以将站点的一些文件缓存到本地,它是浏览器自己的一种机制,将需要的文件缓存下来,以便后期即使没有连接网络,被缓存的页面也可以展示. 2. ...

  4. 基于 SpringBoot 的 FileService

    fileservice file upload download 1.支持多种存储服务器上传.下载 2.支持大文件切片上传 3.存储记录信息使用 redis记录, 文件id可用于与业务数据库关联 4. ...

  5. 【Linux】一步一步学Linux——虚拟机简介和系统要求(04)

    目录 00. 目录 01. VMware Workstation Pro15介绍 02. Workstation Pro 的主机系统要求 03. 虚拟机网络连接支持 04. 参考 00. 目录 @ 0 ...

  6. 安装win7和linux [ubuntu14]双系统

    想体验一把ubuntu18.10最新桌面版的快感,但是windows上面的数据又删除不得,所以百度了一下,win7和linux双系统的安装教程. 一.首先在win7上创建新的分区 https://ji ...

  7. Jsoup配合 htmlunit 爬取异步加载的网页

    加入 jsoup 和 htmlunit 的依赖 <dependency> <groupId>org.jsoup</groupId> <artifactId&g ...

  8. 源码阅读 - java.util.concurrent (四)CyclicBarrier

    CyclicBarrier是一个用于线程同步的辅助类,它允许一组线程等待彼此,直到所有线程都到达集合点,然后执行某个设定的任务. 举个例子:几个人约定了某个地方集中,然后一起出发去旅行.每个参与的人就 ...

  9. input的值为浅淡样式(点击值消失)

    <input type="text" id="leftSearchValue" value="" placeholder=" ...

  10. 解析Unicode转义序列带来的问题

    Unicode转义序列的解析是发生在代码编译之前,编译器机械的将\u样式的代码文本转义,即使是注释以及非正常代码,对此步骤来说也没有区别 导致下面的情况: public class Test { pu ...