摘要

本文介绍了Mock背景,常见的单元测试场景以及对应的测试方法,最后简单介绍了powermockit测试框架。理解本文内容,会带你入门java单元测试,其中第一章、第二章需要重点理解。

一、背景

Mock是什么?

Mock(模拟的)是一种隔离测试类功能的方法。例如:mock测试不需要真实连接数据库,或读取配置文件,或连接服务器。mock对象模拟真实服务,mock对象会返回与传递给它的某个虚拟输入相对应的虚拟数据。

为什么要使用Mock?

单元测试重点在于验证代码逻辑以及结果是否正确,但是在测试过程中经常会遇到如下痛点问题:

  • 接口之间的相互依赖
  • 第三方接口调用,例如连接数据库,连接https等

使用Mock框架可以模拟出外部依赖,只注重测试代码逻辑、验证代码结果,满足测试真实目的。

Mock框架使用流程

  1. 创建 外部依赖 的Mock 对象, 然后将Mock 对象注入到 测试类 中;

  2. 执行 测试代码

  3. 校验 测试代码 是否执行正确。

本文所需的maven依赖

<scope>test</scope>表示仅作用于测试目录。默认作用于范围compile表示被依赖项目需要参与当前项目的编译,包含测试目录。

mvn -Dmaven.test.skip clean install  方式可以不编译测试用例

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope>
</dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.2</version><scope>test</scope>
</dependency>
<dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.2</version><scope>test</scope>
</dependency>

二、单元测试待测内容有哪些分类?

2.1 无框架java代码

使用Junit单元测试包进行测试。

1.被测试类中方法无其他依赖

1)普通无依赖静态方法

静态方法无其他依赖类,可以直接调用方法计算结果。只需要构造期望数据,计算实际数据,最后比对数据。

代码示例:

被测试类:

public class MyMath {public static int add(int num1,int num2) {return num1 + num2;}
}

测试代码:

//测试静态方法@Testpublic void testStaticAdd() {int num1 = 1;int num2 = 1;int expect = 2;int real = MyMath.add(num1, num2);Assert.assertEquals(expect, real);}

2)非静态无依赖方法

代码示例:

被测试类:

public class MyMath {public int multi(int num1,int num2) {return num1 * num2;}
}

测试代码:

@Testpublic void testMulti() {int num1 = 2;int num2 = 3;int expect = 6;int real = new MyMath().multi(num1, num2);Assert.assertEquals(expect, real);}

2.被测试类中方法存在其他可实现依赖

存在其他可实现依赖时,和2.1测试思路一致。

代码示例:

被测试类:

public class MyRectangle {int height;int width;MyMath myMath = new MyMath();public void setHeight(int height) {this.height = height;}public void setWidth(int width) {this.width = width;}public int getArea() {return myMath.multi(width, height);}public int getPerimeter() {return myMath.multi(2, MyMath.add(width, height));}
}

测试代码:

 @Testpublic void testRectangleGetArea() {int width = 2;int height = 3;MyRectangle rectangle = new MyRectangle();rectangle.setWidth(width);rectangle.setHeight(height);int expectedArea = 6;int realArea = rectangle.getArea();Assert.assertEquals(expectedArea, realArea);}

3..被测试类存在难以实现的其他依赖

使用Mock框架,构造出虚拟的依赖,如有需要,可对虚拟依赖进行打桩以满足测试要求。

打桩:用来代替依赖的代码,或未实现的代码。对构造出的虚拟对象,定制其行为,以满足测试逻辑要求。

测试service层代码举例:

service层代码:需要依赖dao层类从数据库获取数据。

public class RectangleService {RectangleDao rectangleDao = new RectangleDao();//rectangleDao未开发完成public int getRectangleAreaById(String id) {MyRectangle myRectangle = rectangleDao.getRectangleById(id);return myRectangle.getArea();}
}

Dao层代码:dao层代码未开发或需要连接数据库,不好操作。

public class RectangleDao {public MyRectangle getRectangleById(String id) {//代码未开发return new MyRectangle();}
}

测试用例代码:mock dao层对象,并通过打桩方式定制其行为。

方式1:将mock的rectangleDao对象,通过java反射方式设置到rectangleService对象中


@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {/*** 此处仅测试RectangleService类代码逻辑,因此该类的依赖需要mock出来,并打桩(自定义对象的行为)*/@Testpublic void testRectangleService() throws Exception{//构造service内部依赖的rectangleDao对象,RectangleDao rectangleDao = PowerMockito.mock(RectangleDao.class);PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));//通过反射的方式,将mock出来的rectangleDao配置到rectangleService中RectangleService rectangleService = new RectangleService();Field field = rectangleService.getClass().getDeclaredField("rectangleDao");field.setAccessible(true);field.set(rectangleService, rectangleDao);//构造期望数据,计算实际数据,比对两者MyRectangle myRectangle = new MyRectangle(2,3);int expectedArea = myRectangle.getArea();int actualArea = rectangleService.getRectangleAreaById("1");Assert.assertEquals(expectedArea, actualArea);}
}

方式2:将mock的rectangleDao对象,通过注解的方式设置到rectangleService对象中

Note:

  • @Mock注解:创建一个Mock对象。
  • @InjectMocks注解:创建一个实例对象,将其他用@Mock、@Spy注解创建的对象注入到用该实例中。
  • @Before注解:junit中的注解,表示每一个@Test注解测试用例执行前,都会执行一遍。
 /**
* 此处仅测试RectangleService类代码逻辑,因此该类的依赖需要mock出来,并打桩(自定义对象的行为)
*/
@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {@InjectMocks  //将其他用@Mock(或@Spy)注解创建的对象设置到下面对象中RectangleService rectangleService;//创建bean(类似new RectangleService)@MockRectangleDao rectangleDao;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);//初始化上面@Mock和@InjectMocks标注对象}@Testpublic void testRectangleService() throws Exception{//打桩PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));//构造期望数据,计算实际数据,比对两者MyRectangle myRectangle = new MyRectangle(2,3);int expectedArea = myRectangle.getArea();//调用实际数据并对比int actualArea = rectangleService.getRectangleAreaById("1");Assert.assertEquals(expectedArea, actualArea);}
}

显然,第2种方式更加方便,尤其是被测试类中依赖许多其他对象时,注解方式更加高效。

2.2 依赖框架的java代码

  • spring-boot框架中,类的依赖通过@Autowired注入,而非new创建,但两者本质一样。在springboot框架中,同样可以使用2.3中的方式进行单元测试。
  • 此外,在springboot框架中还可以使用spring-boot-test包进行单元测试。

1.被测试类不存在难以实现的其他依赖

可直接使用junit测试包对代码逻辑进行测试,参考2.1章节。

2.被测试类存在难以实现的其他依赖

方式1:类似2.1.3,直接使用powermock框架对代码进行测试

代码示例:

service层:依赖dao层方法。

和无框架代码区别在于,springboot框架内开发的bean都交给spring IOC容器管理,使用时直接注入而非new对象,但两者本质一样。

@Service
public class RectangleService {@AutowiredRectangleDao rectangleDao;//rectangleDao未开发完成public int getRectangleAreaById(String id) {MyRectangle myRectangle = rectangleDao.getRectangleById(id);return myRectangle.getArea();}
}

dao层:和数据库交互。

@Component
public class RectangleDao {public MyRectangle getRectangleById(String id) {//代码未开发return new MyRectangle();}
}

测试用例:


@RunWith(PowerMockRunner.class)
public class RectangleServiceTest {@InjectMocks  //将其他用@Mock(或@Spy)注解创建的对象设置到下面对象中RectangleService rectangleService;//创建bean(类似new RectangleService)@MockRectangleDao rectangleDao;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);//初始化上面@Mock和@InjectMocks标注对象}@Testpublic void testRectangleService() throws Exception{//打桩PowerMockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));//构造期望数据,计算实际数据,比对两者MyRectangle myRectangle = new MyRectangle(2,3);int expectedArea = myRectangle.getArea();//调用实际数据并对比int actualArea = rectangleService.getRectangleAreaById("1");Assert.assertEquals(expectedArea, actualArea);}}

方式2:使用spring-boot-test框架对代码进行测试

代码示例:

使用springboot-test的注解。

@RunWith(SpringRunner.class)
@SpringBootTest
public class RectangleServiceTest2 {@Autowired  //注入spring IOC容器管理的beanRectangleService rectangleService;//创建bean(类似new RectangleService)@MockBean  //mock对象RectangleDao rectangleDao;@Testpublic void testRectangleService() throws Exception{//打桩Mockito.when(rectangleDao.getRectangleById("1")).thenReturn(new MyRectangle(2,3));//构造期望数据,计算实际数据,比对两者MyRectangle myRectangle = new MyRectangle(2,3);int expectedArea = myRectangle.getArea();//调用实际数据并对比int actualArea = rectangleService.getRectangleAreaById("1");Assert.assertEquals(expectedArea, actualArea);}}

三、PowerMock框架核心方法

3.1 PowerMock创建mock对象

T PowerMock.mock(Class<T> type);//创建模拟对象,支持final和native方法

public static <T> void spy(Class<T> type);//创建真实对象

代码示例:

//方式1:注解
@Mock
RectangleDao rectangleDao;
//方式2:创建
RectangleDao rectangleDao = PowerMockito.mock(RectangleDao.class);//方式1:注解
@Spy
RectangleDao rectangleDao;
//方式2:创建
RectangleDao rectangleDao = PowerMockito.spy(new RectangleDao);

3.2 对mock对象方法进行打桩

  • 当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。
  • 当需要mock final方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是final方法所在的类。
  • 当需要mock静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。
  • 当需要mock私有方法的时候, 只是需要加注解@PrepareForTest,注解里写的类是私有方法所在的类
  • 当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest和@RunWith。注解里写的类是需要调用系统方法所在的类

(1)mock非final类(接口、普通类、虚基类)的非final方法:

先mock对象,然后对mock的对象进行打桩顶起方法行为,最后比对结果。

public class RectangleTest {@Testpublic void testObjectNormalMethod(){MyRectangle myRectangle = PowerMockito.mock(MyRectangle.class);PowerMockito.when(myRectangle.getArea()).thenReturn(6);int expectArea = 6;int actualArea = myRectangle.getArea();Assert.assertEquals(expectArea,actualArea);}
}

:其中when(...).thenReturn(...)表示,当对象 rectangle 调用 getArea(0)方法,并且参数为 0 时,返回结果为0,这相当于定制了mock 对象的行为结果.

(2)模拟final类或final方法:

final方法所在的类,需要通过@PrepareForTest注解设置,然后mock对象,再然后打桩(指定方法行为),最后对比。

    @Test@PrepareForTest(MyRectangle.class)public void testObjectFinalMethod(){MyRectangle myRectangle = PowerMockito.mock(MyRectangle.class);PowerMockito.when(myRectangle.getFinalArea()).thenReturn(6);int expectArea = 6;int actualArea = myRectangle.getFinalArea();Assert.assertEquals(expectArea,actualArea);}

(3)mockStatic方法

static方法所在的类,需要通过@PrepareForTest注解设置,mock静态方法所在的类,再然后打桩(指定方法行为),最后对比。

public static void mockStatic(Class<?> classToMock);//模拟类的静态方法

    @Test@PrepareForTest(MyRectangle.class)public void testObjectStaticMethod(){PowerMockito.mockStatic(AreaUtils.class);PowerMockito.when(AreaUtils.getStaticArea(new MyRectangle(2,3))).thenReturn(6);int expectArea = 6;int actualArea = AreaUtils.getStaticArea(new MyRectangle(2,3));Assert.assertEquals(expectArea,actualArea);}

(4)mock方法的参数

mock不好实际调用的类,然后打桩mock的参数对象的方法行为,最后对比结果。

AreaUtils类:

public class AreaUtils {public static int getStaticArea(MyRectangle rectangle){return rectangle.height * rectangle.width;}public boolean callArgumentInstance(File file) {return file.exists();}
}

测试代码:

    @Testpublic void testMockObject() {File file = PowerMockito.mock(File.class);//假设file对象不好实现,这里构造一个file对象AreaUtils areaUtils = new AreaUtils();PowerMockito.when(file.exists()).thenReturn(true);//定制file对象的方法行为:file.exists方法时,设置其返回值为trueAssert.assertTrue(areaUtils.callArgumentInstance(file));//传入构造好的file对象。由于造的file对象的file.exists()方法返回值为true,因此调用demo.call方法返回的就是true}

打桩方法总结:

when().thenReturn()

when().thenThrow()

when().thenCallRealMethod()

when(file.exists()).thenThrow(Exception.class);

whenNew(File.class).withArguments("bbb").thenReturn(file);

PowerMock测试框架详解相关推荐

  1. Spring MVC测试框架详解——服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  2. 【07节】Python3+Selenium4自动化 unittest 测试框架详解

    文章目录 1.unittest 框架介绍 2.创建单元测试步骤 3.unittest 模块介绍 3.1 TestCase 类 3.1.1 TestCase 类常用方法: 3.1.2 TestCase ...

  3. Python pytest测试框架详解

    pytest介绍: pytest是一个非常成熟的全功能的Python测试框架: 1.简单灵活,容易上手 2.支持参数化 3.测试用例的skip和xfail,自动失败重试等处理 4.能够支持简单的单元测 ...

  4. websocket接口自动化集成pytest测试框架详解

    目录 websocket协议 1.介绍 2.原理 3.与http协议的比较 4.websocket应用场景 自动化测试资源分享 websocket协议 1.介绍 WebSocket是一种在单个TCP通 ...

  5. shiro放行_Shiro框架详解 tagline

    部分面试资料链接:https://pan.baidu.com/s/1qDb2YoCopCHoQXH15jiLhA 密码:jsam 想获得全部面试必看资料,关注公众号,大家可以在公众号后台回复" ...

  6. WebDriver自动化测试框架详解

    webDriver自动化测试框架详解 一. 简介 WebDriver API相对于Selenium Remote Control API来说,虽然同样是控制浏览器,但它的编程接口更加简洁 WebDri ...

  7. springboot2整合mysql5_SpringBoot2整合SSM框架详解

    SpringBoot2整合SSM框架详解 发布时间:2019-01-15 21:33, 浏览次数:1218 , 标签: SpringBoot SSM <>开发环境 * 开发工具:Eclip ...

  8. (转) shiro权限框架详解06-shiro与web项目整合(上)

    http://blog.csdn.net/facekbook/article/details/54947730 shiro和web项目整合,实现类似真实项目的应用 本文中使用的项目架构是springM ...

  9. 集合框架详解之Set、Map接口与实现类

    集合框架 1.Set集合 1.1Set集合概述 Set实现类:HashSet类.TreeSet类 特点:无序.无下标.元素不可重复 方法:全部继承自Collection中的方法 1.11 Set接口的 ...

最新文章

  1. 【100】新学年的学习安排
  2. 从S3获取数据在html表示,AWS Lambda从DynamoDB加载内容,以S3格式显示在HTML中(示例代码)...
  3. [Spring]IoC容器之进击的注解
  4. 笔记-项目沟通管理-沟通基本原则
  5. vue html2canvas用法,vue中html2canvas的使用(地图截图)
  6. 软件登录界面设计分享
  7. Apache Shiro java安全框架
  8. CSS3的线性渐变(linear-gradient)
  9. java web获取请求体内容
  10. cloudflare免费设置_Cloudflare 入门教程:使用 Cloudflare 免费 CDN 加速 amp; 保护自己的网站...
  11. 斐讯k2路由器刷PandoraBox一宽带多人用
  12. 空时编码的理论与实践(3)空时编码设计准则
  13. ZZULIOJ 1190: 按出生日期排序(结构体专题)
  14. 务实至上:“PHP之父”Rasmus Lerdorf访谈录
  15. ADS 2019 安装 仿真
  16. 计算机应用办公自动化技术,计算机应用技术有哪些课程【计算机信息传输技术在办公自动化的应用】...
  17. 中科断网神器的功能及界面
  18. 注册表修改3389端口号
  19. 2012最新超全个性签名,走过路过不要错过哦!
  20. 模糊PID(重心法解模糊梯形图FC)

热门文章

  1. 微信小程序[ app.json 文件内容错误] app.json: app.json 未找到解决方法
  2. Python判断字符串相等
  3. python 判断list 中重复元素最后1次出现的位置
  4. 鸿蒙系统 p40 尺寸,P40尺寸
  5. < 每日知识点:关于Javascript 精进小妙招 ( Js技巧 ) >
  6. WindTerm 开源的高性能终端模拟器 最酷
  7. 对于“优盘变成了RAW的文件系统,双击提示需要格式化,右键查看属性是0字节,也无法打开”的解决办法
  8. 用github教育优惠申请jetbrains学生认证时遇到的问题
  9. 腾讯云机器学习平台TI-ONE
  10. 蓝桥杯 基础练习 闰年判断