概述

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

依赖引入

  1. <dependency>
  2. <groupId>org.powermock</groupId>
  3. <artifactId>powermock-module-junit4-rule-agent</artifactId>
  4. <version>1.6.6</version>
  5. <scope>test</scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.powermock</groupId>
  9. <artifactId>powermock-api-mockito</artifactId>
  10. <version>1.6.6</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.powermock</groupId>
  14. <artifactId>powermock-module-junit4</artifactId>
  15. <version>1.6.6</version>
  16. </dependency>

被测试类

  1. public class PowerMockitoDemo {
  2.  
  3. @Autowired
  4. private StudentDao studentDao;
  5.  
  6. @Autowired
  7. private TeacherService teacherService;
  8.  
  9. public void study() {
  10. //doSomething
  11. }
  12.  
  13. private void play(Map<String, Object> project, Person person, int hours) {
  14. //doSomething
  15. }
  16.  
  17. private boolean updateStudentName(String newName) {
  18. //doSomething
  19. }
  20.  
  21. public ServiceResult grantRights(List<String> usernames, String rights, String orderid, String id) {
  22. String value1 = PropertiesUtil.get("key1");
  23. String value2 = PropertiesUtil.get("key2");
  24. studentDao.saveRecord(usernames);//返回值类型void
  25. Student student = studentDao.getStudentById(id);
  26. boolean result = this.verifyParams(usernames);
  27. teacherService.syncDB2Redis(usernames, orderid);
  28. this.updateOperation(rights);
  29. }
  30.  
  31. private boolean verifyParams(List<String> usernames) {
  32. //doSomething
  33. }
  34.  
  35. private void updateOperation(String rights) {
  36. //doSomething
  37. }
  38. }

测试用例基类

  1. //@PrepareForTest注解和@RunWith注解需结合使用,单独使用将不起作用
  2. @RunWith(PowerMockRunner.class)
  3. @PrepareForTest({RedisUtils.class})
  4. @SuppressStaticInitializationFor({"com.test.util.RedisUtils", "com.test.util.HttpUtils"})//用于阻止类中的静态代码块执行
  5. public abstract class BaseTest {
  6.  
  7. public RedisUtils redisUtils;
  8.  
  9. @Rule
  10. public ExpectedException thrown = ExpectedException.none();//断言要抛出的异常
  11.  
  12. public void setUp() {
  13. initMocks(this);
  14.  
  15. PowerMockito.suppress(PowerMockito.constructor(ShardedJedisClientImpl.class, String.class));
  16. redisUtils = PowerMockito.mock(RedisUtils.class);
  17. Whitebox.setInternalState(RedisUtils.class, "redisUtil", redisUtils);//给类或实例对象的成员变量设置模拟值,这里是给RedisUtils类中的字段redisUtil设置模拟值
  18.  
  19. PowerMockito.suppress(PowerMockito.constructor(HttpUtils.class));
  20. PowerMockito.mockStatic(HttpUtils.class);//mock类中所有静态方法
  21. }
  22.  
  23. /**
  24. * @param instance 真实对象
  25. * @param methodName 方法名
  26. * @param args 形参列表
  27. */
  28. public Object callPrivateMethod(Object instance, String methodName, Object... args) throws Exception {
  29. return Whitebox.invokeMethod(instance, methodName, args);//调用私有方法
  30. }
  31.  
  32. }

测试用例

  1. @PrepareForTest({PowerMockitoDemo.class, PropertiesUtil.class})//此处PowerMockitoDemo被测试类添加到@PrepareForTest注解中, 用于mock其静态、final修饰及私有方法;另外,PropertiesUtil工具类由于不通用,不适合抽取到基类BaseTest中, 可在子类mock
  2. @SuppressStaticInitializationFor("com.test.util.PropertiesUtil")//用于阻止类中的静态代码块执行
  3. public class PowerMockitoDemoTest extends BaseTest{
  4.  
  5. @org.powermock.core.classloader.annotations.Mock
  6. private StudentDao studentDao;
  7.  
  8. @org.powermock.core.classloader.annotations.Mock
  9. private TeacherService teacherService;
  10.  
  11. @org.powermock.core.classloader.annotations.Mock
  12. @InjectMocks
  13. private PowerMockitoDemo powerMockitoDemo;
  14.  
  15. @Override
  16. @Before
  17. public void setUp() {
  18. super.setUp();
  19. PowerMockito.suppress(PowerMockito.constructor(PropertiesUtil.class));
  20. PowerMockito.mockStatic(PropertiesUtil.class);//mock类中所有静态方法
  21. }
  22.  
  23. @Test
  24. public void studyWhenCallSuccessfully() {
  25. PowerMockito.doCallRealMethod().when(powerMockitoDemo).study();
  26. //doSomething
  27.  
  28. powerMockitoDemo.study();
  29. }
  30.  
  31. @Test
  32. public void playWhenCallSuccessfully() {
  33. Map<String, Object> project = new HashMap<String, Object>();
  34. Person person = new Person();
  35. int hours = 8;
  36.  
  37. PowerMockito.doCallRealMethod().when(powerMockitoDemo, "play", Matchers.anyMapOf(String.class, Object.class), Matchers.any(Person.class), Matchers.anyInt());
  38. //doSomething
  39.  
  40. this.callPrivateMethod(powerMockitoDemo, "play", project, person, hours);
  41. }
  42.  
  43. @Test
  44. public void updateStudentNameWhenCallSuccessfully() throws Exception {
  45. String id = "9527";
  46.  
  47. PowerMockito.when(powerMockitoDemo, "updateStudentName", Matchers.anyString()).thenCallRealMethod();
  48. //doSomething
  49.  
  50. boolean actualResult = this.callPrivateMethod(powerMockitoDemo, "updateStudentName", id);
  51. Assert.assertTrue(actualResult == true);
  52. }
  53.  
  54. @Test
  55. public void getStudentByIdWhenCallSuccessfully() throws Exception {
  56. List<String> usernames = new ArrayList<String>();
  57. String rights = "万叶飞花流";
  58. String orderid = "orderid";
  59. String id = "id";
  60.  
  61. //调用真实方法
  62. PowerMockito.when(powerMockitoDemo.grantRights(Matchers.anyListOf(String.class), Matchers.anyString(), Matchers.anyString(), Matchers.anyString())).thenCallRealMethod();
  63. //当方法内重复调用同一个方法时, 可通过Matchers.eq()方法来指定实际入参来加以区分
  64. PowerMockito.when(PropertiesUtil.get(Matchers.eq("key1"))).thenReturn("value1");
  65. PowerMockito.when(PropertiesUtil.get(Matchers.eq("key2"))).thenReturn("value2");
  66. //返回值类型为void,不做任何事情
  67. PowerMockito.doNothing().when(studentDao).saveRecord(Matchers.anyListOf(String.class));
  68. //类似需要调用数据库、redis、远程服务的,可直接模拟返回值,不做方法的真实调用
  69. PowerMockito.when(studentDao.getStudentById(Matchers.anyString())).thenReturn(new Student());
  70. //调用真实的私有方法
  71. PowerMockito.when(powerMockitoDemo, "verifyParams", Matchers.anyListOf(String.class)).thenCallRealMethod();
  72. //模拟私有方法返回值
  73. PowerMockito.when(powerMockitoDemo, "verifyParams", Matchers.anyListOf(String.class)).thenReturn(true);
  74. //模拟方法调用抛出异常。当被调用的方法头没有显式声明异常时, 则mock只支持unchecked exception,比如这里syncDB2Redis()方法签名没有声明任何异常,则thenThrow()模拟异常时只支持模拟运行时异常,使用非运行时异常将编译不通过
  75. PowerMockito.when(teacherService.syncDB2Redis(Matchers.anyListOf(String.class), Matchers.anyString())).thenThrow(new RuntimeException("Failed to call remote service"));
  76. PowerMockito.doNothing().when(powerMockitoDemo, "updateOperation", Matchers.anyString());
  77.  
  78. ServiceResult expectedResult = new ServiceResult();
  79. ServiceResult actualResult = powerMockitoDemo.grantRights(usernames, rights, orderid, id);
  80. //断言实际调用结果是否符合预期值
  81. Assert.assertEquals(JSON.toJSONString(expectedResult), JSON.toJSONString(actualResult));
  82. }
  83. }

如上, 具体的阐释在代码注释中都已经标注, 抽取基类是为了提高代码可重用性。博主这里是为了演示, 所以代码看起来会有点臃肿, 在实际项目使用中, 可以通过静态引入 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. Demo小细节

    (1) 程序如下: public class Example { static int i = 1, j = 2; static { display(i); i = i + j; } static v ...

  2. Windows 10使用Tesseract-OCR出现WindowsError: [Error 2]

    Tesseract-OCR安装时默认安装在x86的目录下,手动添加环境变量此电脑-->属性-->高级系统设置-->环境变量,点击系统变量里的Path, 点击编辑,在编辑环境变量界面中 ...

  3. jmeter性能测试前及测试后

    压测前:           1.压力测试两种场景:                    1)单场景,压测单个接口. 2)混合场景,多个接口关联压测. 2.压测时间:                ...

  4. Python笔记【4】_字典学习

    #!/usr/bin/env/python #-*-coding:utf-8-*- #Author:LingChongShi #查看源码Ctrl+左键 ''' dict:字典以“{}”包围,以“键:值 ...

  5. Linux 安装Nginx与使用

    最近继续整理Linux相关文档.这次整理的是Nginx,这里将自己整理的详细文档做个笔记. 1.  安装环境依赖包 1. gcc 语言编译器套件. 2. pcre 兼容正则表达式的库 rewrite ...

  6. 源码阅读 - java.util.concurrent (二)CAS

    背景 在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. ...

  7. SPOJ STC02 - Antisymmetry(Manacher算法求回文串数)

    http://www.spoj.com/problems/STC02/en/ 题意:给出一个长度为n的字符串,问其中有多少个子串s可以使得s = s按位取反+翻转. 例如样例:11001011. 10 ...

  8. 微服务SpringCloud之熔断器

    学习SpringCloud微服务是参考纯洁的微笑博客,看到他提到股市的熔断我也忍不住吐槽一下,记得当时实施熔断第一天就熔断了,现在想想也还是搞笑,从之前的全民炒股到现在的全民炒房,都是一个炒字,问题是 ...

  9. Spring MVC源码(一) ----- 启动过程与组件初始化

    SpringMVC作为MVC框架近年来被广泛地使用,其与Mybatis和Spring的组合,也成为许多公司开发web的套装.SpringMVC继承了Spring的优点,对业务代码的非侵入性,配置的便捷 ...

  10. Bzoj 2058: [Usaco2010 Nov]Cow Photographs 题解

    2058: [Usaco2010 Nov]Cow Photographs Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 190  Solved: 104[ ...