powermockito单元测试之深入实践
概述
由于最近工作需要, 在项目中要做单元测试, 以达到指定的测试用例覆盖率指标。项目中我们引入的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深入实践" ^_^, 后面如有接触新的相关知识点, 会陆续更新到本篇文章中。如有错误, 欢迎指正, 谢谢你^_^
参考资料
powermockito单元测试之深入实践的更多相关文章
- [转载]单元测试之道(使用NUnit)
首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而又忐忑的心情点击界面上的 ...
- 单元测试之道(使用NUnit)
首先来看下面几个场景你是否熟悉 1.你正在开发一个系统,你不断地编码-编译-调试-编码-编译-调试……终于,你负责的功能模块从上到下全部完成且编译通过!你长出一口气,怀着激动而 又忐忑的心情点击界面上 ...
- 补习系列(8)-springboot 单元测试之道
目录 目标 一.About 单元测试 二.About Junit 三.SpringBoot-单元测试 项目依赖 测试样例 四.Mock测试 五.最后 目标 了解 单元测试的背景 了解如何 利用 spr ...
- 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单元测试 ...
- iOS 单元测试之XCTest详解(一)
iOS 单元测试之XCTest详解(一) http://blog.csdn.net/hello_hwc/article/details/46671053 原创blog,转载请注明出处 blog.csd ...
- 玩转单元测试之Testing Spring MVC Controllers
玩转单元测试之 Testing Spring MVC Controllers 转载注明出处:http://www.cnblogs.com/wade-xu/p/4311657.html The Spri ...
- 玩转单元测试之WireMock -- Web服务模拟器
玩转单元测试之WireMock -- Web服务模拟器 WireMock 是一个灵活的库用于 Web 服务测试,和其他测试工具不同的是,WireMock 创建一个实际的 HTTP服务器来运行你的 We ...
- 单元测试之NSNull 检测
本文主要讲 单元测试之NSNull 检测,在现实开发中,我们最烦的往往就是服务端返回的数据中隐藏着NSNull的数据,一般我们的做法是通过[data isKindOfClass:[NSNull cla ...
- 使用VisualStudio进行单元测试之二
借着工作忙的借口,偷了两天懒,今天继续单元测试之旅.前面说了如何进行一个最简单的单元测试,这次呢就跟大家一起来熟悉一下,在visual studio中如何进行数据驱动的单元测试. 开始之前先来明确一下 ...
随机推荐
- Demo小细节
(1) 程序如下: public class Example { static int i = 1, j = 2; static { display(i); i = i + j; } static v ...
- Windows 10使用Tesseract-OCR出现WindowsError: [Error 2]
Tesseract-OCR安装时默认安装在x86的目录下,手动添加环境变量此电脑-->属性-->高级系统设置-->环境变量,点击系统变量里的Path, 点击编辑,在编辑环境变量界面中 ...
- jmeter性能测试前及测试后
压测前: 1.压力测试两种场景: 1)单场景,压测单个接口. 2)混合场景,多个接口关联压测. 2.压测时间: ...
- Python笔记【4】_字典学习
#!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 ''' dict:字典以“{}”包围,以“键:值 ...
- Linux 安装Nginx与使用
最近继续整理Linux相关文档.这次整理的是Nginx,这里将自己整理的详细文档做个笔记. 1. 安装环境依赖包 1. gcc 语言编译器套件. 2. pcre 兼容正则表达式的库 rewrite ...
- 源码阅读 - java.util.concurrent (二)CAS
背景 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. ...
- SPOJ STC02 - Antisymmetry(Manacher算法求回文串数)
http://www.spoj.com/problems/STC02/en/ 题意:给出一个长度为n的字符串,问其中有多少个子串s可以使得s = s按位取反+翻转. 例如样例:11001011. 10 ...
- 微服务SpringCloud之熔断器
学习SpringCloud微服务是参考纯洁的微笑博客,看到他提到股市的熔断我也忍不住吐槽一下,记得当时实施熔断第一天就熔断了,现在想想也还是搞笑,从之前的全民炒股到现在的全民炒房,都是一个炒字,问题是 ...
- Spring MVC源码(一) ----- 启动过程与组件初始化
SpringMVC作为MVC框架近年来被广泛地使用,其与Mybatis和Spring的组合,也成为许多公司开发web的套装.SpringMVC继承了Spring的优点,对业务代码的非侵入性,配置的便捷 ...
- Bzoj 2058: [Usaco2010 Nov]Cow Photographs 题解
2058: [Usaco2010 Nov]Cow Photographs Time Limit: 3 Sec Memory Limit: 64 MBSubmit: 190 Solved: 104[ ...