- PowerMock介绍
Mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个虚拟mock数据对象来模拟对象的行为。在现实的软件开发过程中,我们经常需要协同其他同事一起来完成某个模块的功能开发,或者需要第三方资源,比如您需要一个短信网关,您需要一个数据库,您需要一个消息中间件,当您进行测试依赖于这些资源的代码时候,不可能在每次都具备相应的环境,这将Programming 系列丛书是一个很不现实的问题,如果当您依赖于其他模块而无法进行单元测试的时候,此时该模块的质量风险就有两个,第一是您所负责的代码,第二是您所依赖的代码,您所依赖您没有办法在很快的时间协调到资源,那么您所负责的代码由于不具备单元测试环境没有办法进行测试,很可能存在极大的风险,因此如何测试您的代码,让他的质量达到百分之百的可用,这就是 Mock 存在的必要。
当前市场上比较成熟的Mock框架主要有Mockito、EasyMock、Jmockit、PowerMock、Spock等等,SpringBoot默认的Mock框架是Mockito,和junit一样,只需要依赖spring-boot-starter-test就可以了。 - PowerMock入门
PowerMock 现在目前提供了两套 UT(Unit Test)框架的封装,请看下图
本文中采用的是 Junit+PowerMock+Mockito 这样的组合来进行讲述的。
2.1 依赖
Maven依赖如下:
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
2.2 Mock局部变量
首先新建一个员工类
package com.example.powermock.demo;
import lombok.Data;
/**
- @description 员工类
- @version V1.0
- @date 2022/8/15 15:55
**/
@Data
public class Employee {
private Long id;
private String name;
private String hobbies;
private String mark;
}
再新增一个模拟数据库操作的Dao(接口方法用抛异常的方式模拟该接口不可用)
package com.example.powermock.demo.local.dao;
import com.example.powermock.demo.Employee;
/**
- @description 员工Dao
- @version V1.0
- @date 2022/8/15 15:55
**/
public class EmployeeDao {
public int getTotal()
{
throw new UnsupportedOperationException();
}
public void addEmployee(Employee employee)
{
throw new UnsupportedOperationException();
}
}
2.2.1 有返回值
有如下Service的情况,在编码中经常遇到,但是其他Mock却对此束手无策:
package com.example.powermock.demo.local.service;
import com.example.powermock.demo.Employee;
import com.example.powermock.demo.local.dao.EmployeeDao;
/**
- @description 员工service
- @version V1.0
- @date 2022/8/15 15:55
**/
public class EmployeeService {
public EmployeeService() {}
/**
* 获取所有员工的数量.
* @return
*/
public int getTotalEmployee()
{
EmployeeDao employeeDao = new EmployeeDao();
return employeeDao.getTotal();
}
}
我们采用在方法内部new出一个 EmployeeDao,而要测试这样的代码 EasyMock,Mockito 等显然力不从心,而PowerMock却毫无压力,请看下面的测试代码
package com.example.powermock.demo;
import com.example.powermock.demo.local.dao.EmployeeDao;
import com.example.powermock.demo.local.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class)
public class EmployeeServiceLocalTest {
// 传统方法测试不通过
@Test
public void testGetTotalEmployee() {
final EmployeeService service = new EmployeeService();
int total = service.getTotalEmployee();
assertEquals(10, total);
}
// PowerMock测试可以通过
@Test
public void testGetTotalEmployeeWithMock() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
try {
PowerMockito.whenNew(EmployeeDao.class).withNoArguments().thenReturn(employeeDao);
PowerMockito.when(employeeDao.getTotal()).thenReturn(10);
EmployeeService service = new EmployeeService();
int total = service.getTotalEmployee();
assertEquals(10, total);
} catch (Exception e) {
fail("测试失败");
}
}
}
2.2.2 void局部变量
在Servcie中增加一个void方法
public void createEmployee(Employee employee)
{
EmployeeDao employeeDao = new EmployeeDao();
employeeDao.addEmployee(employee);
}
Mock测试代码如下
@Test
public void testCreateEmployeeWithMock() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
try {
PowerMockito.whenNew(EmployeeDao.class).withNoArguments()
.thenReturn(employeeDao);
Employee employee = new Employee();
EmployeeService service = new EmployeeService();
service.createEmployee(employee);
Mockito.verify(employeeDao).addEmployee(employee);
} catch (Exception e) {
fail();
}
}
2.3 Mock Static
对于公共方法,我们习惯于将其封装到工具类中,而工具类中的方法一般为static方法。
定义一个Service,定义两个方法,具体如下
package com.example.powermock.demo.staticmock.service;
import com.example.powermock.demo.Employee;
import com.example.powermock.demo.helloworld.dao.EmployeeDao;
import com.example.powermock.demo.staticmock.EmployeeUtils;
/**
- @description 员工service
- @version V1.0
- @date 2022/8/15 15:55
**/
public class EmployeeService {
/**
* 获取所有员工的数量.
* @return
*/
public int getTotalEmployee()
{
return EmployeeUtils.getEmployeeCount();
}
/**
* 新增员工
* @param employee
*/
public void createEmployee(Employee employee)
{
EmployeeUtils.persistenceEmployee(employee);
}
}
我们的工具类Employee代码如下:
package com.example.powermock.demo.staticmock;
import com.example.powermock.demo.Employee;
public class EmployeeUtils {
// 获取员工数量
public static int getEmployeeCount()
{
throw new UnsupportedOperationException();
}
//新增员工
public static void persistenceEmployee(Employee employee)
{
throw new UnsupportedOperationException();
}
}
用常规的方式无法对static方法进行测试,而PowerMock对此游刃有余,代码如下
package com.example.powermock.demo;
import com.example.powermock.demo.staticmock.EmployeeUtils;
import com.example.powermock.demo.staticmock.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.junit.Assert.assertEquals;
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeUtils.class)
public class EmployeeServiceStaticTest {
@Test
public void testGetTotalEmployeeWithMock() {
PowerMockito.mockStatic(EmployeeUtils.class);
PowerMockito.when(EmployeeUtils.getEmployeeCount()).thenReturn(10);
final EmployeeService employeeService = new EmployeeService();
int count = employeeService.getTotalEmployee();
assertEquals(10, count);
}
@Test
public void testCreateEmployeeWithMock() {
PowerMockito.mockStatic(EmployeeUtils.class);
Employee employee = new Employee();
PowerMockito.doNothing().when(EmployeeUtils.class);
final EmployeeService employeeService = new EmployeeService();
employeeService.createEmployee(employee);
PowerMockito.verifyStatic();
}
}
2.4 Mock final
一般的测试框架对final修饰的类或方法同样是束手无策,待测试接口如下:
Service
package com.example.powermock.demo.mockfinal.service;
import com.example.powermock.demo.Employee;
import com.example.powermock.demo.mockfinal.dao.EmployeeDao;
/**
- @description 员工service
- @version V1.0
- @date 2022/8/15 15:55
**/
public class EmployeeService {
private EmployeeDao employeeDao;
public EmployeeService(EmployeeDao employeeDao)
{
this.employeeDao = employeeDao;
}
public void createEmployee(Employee employee)
{
employeeDao.addEmployee(employee);
}
}
带有final修饰接口的DAO
package com.example.powermock.demo.mockfinal.dao;
import com.example.powermock.demo.Employee;
/**
- @description 员工Dao
- @version V1.0
- @date 2022/8/15 15:55
**/
public class EmployeeDao {
public final boolean addEmployee(Employee employee)
{
throw new UnsupportedOperationException();
}
}
测试代码如下:
package com.example.powermock.demo;
import com.example.powermock.demo.mockfinal.dao.EmployeeDao;
import com.example.powermock.demo.mockfinal.service.EmployeeService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeDao.class)
public class EmployeeServiceFinalTest {
@Test
public void testCreateEmployeeWithMock() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
Employee employee = new Employee();
Mockito.when(employeeDao.addEmployee(employee)).thenReturn(true);
EmployeeService employeeService = new EmployeeService(employeeDao);
employeeService.createEmployee(employee);
Mockito.verify(employeeDao).addEmployee(employee);
}
}
3 总结
PowerMock作为其他Mock框架的补充和扩展,有其自身独到的优势:final 类型的 class 和 method 的 mock 操作;完成对类方法(static)的 mock;完成对局部变量的 mock 等等。总之一句话:PowerMock 的出现为解决此类疑难问题另辟蹊径。