文章目录
1. PowerMock 介绍
1.1 简介
PowerMock 主要围绕着 Junit 测试框架和 TestNg 测试框架进行,其中在每个框架下面对所涉及的 Mock 框架仅仅支撑 EasyMock 和 Mockito,为什么要画这个图呢,是因为PowerMock 对所依赖的 Jar 包非常的苛刻,如果出现某个依赖包的冲突或者不一致都会出现不能使用的情况,因此根据您的喜好和擅长,请选择官网上提供的 PowerMock 套件
1.2 解决了什么问题
1.3 如何获得 PowerMock
您可以在 PowerMock 的官方网站免费获得测试组件,我之前说过 PowerMock 对所依赖的 library 有些苛刻,因此最好还是老老实实用它提供的套件包,如下所示,您可以下载我红色标注出来的测试套件。
或者使用maven
<properties>
<powermock.version>1.5.6</powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
2. PowerMock 入门
2.1 使用场景
在现实的软件开发过程中,我们经常需要协同其他同事一起来完成某个模块的功能开发,或者需要第三方资源,比如您需要一个短信网关,您需要一个数据库,您需要一个消息中间件,当您进行测试依赖于这些资源的代码时候,不可能在每次都具备相应的环境,这将是一个很不现实的问题,如果当您依赖于其他模块而无法进行单元测试的时候,此时该模块的质量风险就有两个,第一是您所负责的代码,第二是您所依赖的代码,您所依赖您没有办法在很快的时间协调到资源,那么您所负责的代码由于不具备单元测试环境没有办法进行测试,很可能存在极大的风险,因此如何测试您的代码,让他的质量达到百分之百的可用,这就是 Mock 存在的必要,如果您还不能清楚这个场景,我们举一个大家都耳熟能详的例子来探讨一下。
采用三层架构的方式实现某个 WEB 的请求到业务受理,已经变得非常成熟,甚至现在在更多的分层,更多的细化每一个层次的职责,尽量使某一个层次可以最大化的重用,减少耦合,下图您应该非常熟悉了吧。
那么请大家思考如下几个问题:
1、如何保证各个层次模块的代码完全可用,在正常和临界情况下?
2、您使用集成测试做单元测试么?
很多人所谓的测试恐怕更多的是一种集成测试,也就是点击页面某个按钮看看是否能够顺利执行,所依赖的各种资源都必须正常运行,如果出现问题就很难让整个流程执行下去,如上图所示,我们如何在没有数据库的时候能够测试我们的 Service,没有 Payment Gate的时候进行支付的测试等等,这才是 Mock 要解决的问题,请注意是 Mock 而不是PowerMock(PowerMock 也是一种 Mock,但是他主要是解决其他 Mock 不能解决的问题)
2.2 PowerMock 之 HelloWorld
2.2.1 获取所有员工的个数
我们现在有一个 Service 类,就是 EmployeeService,其中有一个方法需要获取数据库中雇员的数量,Service 代码如下所示
public class EmployeeService {
private EmployeeDao employeeDao;
public EmployeeService(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
/**
* 获取所有员工的数量. * @return
*/
public int getTotalEmployee(){
return employeeDao.getTotal();
}
}
可以看到,创建 Service 的时候需要传递一个 EmployeeDao 这个类,也就是说 Service
依赖于 Persistence,如果想要测试 Service 就需要完全看 Persistence 的脸色,我们再来看看 Persistence 代码,如下所示
public class EmployeeDao {
public int getTotal(){
throw new UnsupportedOperationException();
}
}
这个时候肯定调用不了 Dao,无法正常完成 Service 的测试,那么为什么要在Persistence的方法抛出UnsupportedOperationException呢?目的就是告诉大家该方法可能由于某种原因(没有完成,或者资源不存在等)无法为 Service 服务,难道你不需要测
试 EmployeeService 么?肯定要测试,那么我们就硬着头皮来写测试用例吧
public class EmployeeServiceTest {
@Test
public void testGetTotalEmployee() {
final EmployeeDao employeeDao = new EmployeeDao();
final EmployeeService service = new EmployeeService(employeeDao);
int total = service.getTotalEmployee();
assertEquals(10, total);
}
}
上面的测试肯定会是以失败收场
此刻抛出来的异常,就好像是此时此刻数据库连接不上,问题现在很明显,数据库链接不通,我们无法测试 Service,难道真的就无计可施了么?
public class EmployeeServiceTest {
@Test
public void testGetTotalEmployeeWithMock() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
PowerMockito.when(employeeDao.getTotal()).thenReturn(10);
EmployeeService service = new EmployeeService(employeeDao);
int total = service.getTotalEmployee();
assertEquals(10, total);
}
}
当你再次运行时你会发现此时此刻运行通过,编写一下上述的代码我们先来有个简单的认识,所谓 Mock 就是创建一个假的,Mock 那个对象就会创建一个假的该对象,此时该对象是一单纯的白纸,需要你对其进行涂鸦,第二句话 when…then 语法就是您的涂鸦,您期望调用某个方法的时候返回某个您指定的值,最后的代码已经非常熟悉了,此时此刻您已经完全让EmployeeDao 根据你的意愿来运行,所以想怎样测试 EmployeeService 就怎样测试。
2.2.2 创建员工
我们再来增加另外一个需求,就是创建一个 Employee,这就意味着我们需要分别在Service 和 Dao 中增加相应的两个接口,请看代码如下所示
Service 中的 CreateEmployee 方法
public void createEmployee(Employee employee) {
employeeDao.addEmployee(employee);
}
再来看看 Dao 中的方法 addEmployee
public void addEmployee(Employee employee) {
throw new UnsupportedOperationException();
}
至于addEmployee为什么抛出异常,在不多做解释了,同样如果针对createEmployee写单元测试运行结果肯定死得很惨,因为此时“数据库资源不存在”,相信大家一定很清楚这一点,但是这不是本小节中所要讲述的重点,重点在于 addEmployee 方法是一个 void类型的,也就是我们没有办法断言想要的结果是否一致,而 mock 厚的 addEmployee 方法事实上是什么都不会做的,此时我们该如何进行测试呢?
简单思考一下我们其实只是想要知道 addEmployee 方法是否被调用过即可,当然我们可以假设他 add 成功或者失败,这就根据您的 test case 来设定了,好了,有了这个概念之后我们来看看如何测试 void 方法,其实就是 mock 中一个很重要的概念 Verifying 了。
@Test
public void testCreateEmployee() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
Employee employee = new Employee();
PowerMockito.doNothing().when(employeeDao).addEmployee(employee);
EmployeeService service = new EmployeeService(employeeDao);
service.createEmployee(employee);
// verify the method invocation.
Mockito.verify(employeeDao).addEmployee(employee);
}
然后用 junit 运行肯定能够通过,其中 Mockito.verify 主要用来校验被 mock 出来的对象中的某个方法是否被调用,我们的 PowerMock helloworld 也到此结束了。
2.3 重点 API 解释
2.3.1 Mock
Powermockito.mock() 方 法 主 要 是 根 据 class 创 建 一 个 对 应 的 mock 对 象 ,powermock 的创建方式可不像 easymock 等使用 proxy 的方式创建,他是会在你运行的过程中动态的修改 class 字节码文件的形式来创建的。
2.3.2 Do…when…then
我们可以看到,每次当我们想给一个 mock 的对象进行某种行为的预期时,都会使用do…when…then…这样的语法,其实理解起来非常简单:做什么、在什么时候、然后返回什么。
2.3.3 Verify
当我们测试一个 void 方法的时候,根本没有办法去验证一个 mock 对象所执行后的结果,因此唯一的方法就是检查方法是否被调用,在后文中将还会专门来讲解。
3. Mock Local Variable
3.1 有返回值
先来看看 Service 的代码
public class EmployeeService {
public int getTotalEmployee() {
EmployeeDao employeeDao = new EmployeeDao();
return employeeDao.getTotal();
}
}
我们并没有采用 hello world 中通过构造方法注入 dao 的方式,而是在方法内部 new出一个 EmployeeDao,我们通常都会写这样的代码,根据之前的经验,调用肯定是失败的,而要测试这样的代码 EasyMock,Mockito 等显然力不从心,这个时候优势就很明显的体现出来了,请看下面的测试代码
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class)
public class EmployeeServiceTest {
/**
* 用传统的方式肯定测试失败。
*/
@Test
public void testGetTotalEmployee() {
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("测试失败.");
}
}
}
运行上面的代码可以很明显的看到其中传统的方式是失败的,而通过 Mock 方式测试的是成功的,当然您可能会非常惊讶 PowerMock 尽然有如此强大的能力,以至于可以Mock 出局部变量,那么同样我们再来看看局部变量的 void 方法该如何进行测试。
3.2 局部变量的 void 方法
同样在 Service 中增加一个添加 Employee 的方法
public void createEmployee(Employee employee) {
EmployeeDao employeeDao = new EmployeeDao();
employeeDao.addEmployee(employee);
}
我们该如何测试这个方法呢?同样的如果采用之前的传统调用方式肯定会出现错误,我们只能采用 PowerMock 的方式来进行测试,测试代码如下所示
@Test
public void testCreateEmployee() {
EmployeeService service = new EmployeeService();
Employee employee = new Employee();
service.createEmployee(employee);
}
@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();
}
}
运行上面的两个测试用例,你会发现其中第一个失败,第二个运行成功,对于测试 void返回类型的方法,同样我们做的只能是判断他是否被调用
3.3 @RunWith 和@PrepareForTest 介绍
其中**@RunWith** 就是显式的告诉 Junit 使用某个指定的 Runner 来运行 Test Case,我们使用了 PowerMockRunner 来运行我们的测试用例,如果不指定的话我们就默认使用的是 Junit 提供的 Runner,先来看看下面这张图,了解一下 Runner 家族的成员
由于我们在测试EmployeeService的时候需要改变EmployeeDao的创建行为,通过这个的语句PowerMockito.whenNew(EmployeeDao.class).withNoArguments().thenReturn(
employeeDao);就可以看得出来,因此就需要了解另外一个注解@PrepareForTest,这个
注解是告诉PowerMock为我提前准备一个xxx的class,根据我测试预期的行为去准备,根据这段粗糙的文字描述,我们再来详细的整理一下。
在测试EmployeeService的时候,因为涉及到了局部变量的创建,我们必须让他创建成为我们想要的Mock对象,因此需要有一个录制的过程PowerMockito.whenNew( EmployeeDao.class).withNoArguments().thenReturn(employeeDao); 这 句 话 就 是 告 诉Power Mock录制一个这样的构造行为,等我接下来用的时候你就把之前mock的东西给我, 而@PrepareForTest是为PowerMock的Runner提前准备一个已经根据某种预期改变过的class,在这个例子中就是EmployeeService,如果大家使用过Jacoco统计过Java测试代码的覆盖率就会发现一个很恼火的问题,那就是不管你写了多少个EmployeeService的测试用例,只要你对其进行了PrepareForTest之后,代码覆盖率仍然是0,为什么会这样呢,因为经过了PrepareForTest的处理之后,PowerMock框架会通过类加载器的方式改变EmployeeService的行为,如何改变呢?当然是产生了一个新的类在运行的过程中,新的类里面但凡碰到要创建EmployeeDao的时候都会将提前录制好的行为重播出来,给出一个Mock对象。
4. Mock Static
有时候我们写代码的时候喜欢编写已经写工具类,工具类一般都提供了大量的便捷的类方法(static)供调用者使用,在本章中我们来看看如何进行静态方法的测试
4.1 问题场景
同样 Service 还是两个接口,第一个是查询雇员数量,第二个是创建一个新的雇员,代码如下所示
public class EmployeeService {
public int getEmployeeCount() {
return EmployeeUtils.getEmployeeCount();
}
public void createEmployee(Employee employee) {
EmployeeUtils.persistenceEmployee(employee);
}
}
相比这样的代码在前文中已经看得非常多了,重点是其中的一个工具类 EmployeeUtils,同样来看看它的代码
public class EmployeeUtils {
public static int getEmployeeCount() {
throw new UnsupportedOperationException();
}
public static void persistenceEmployee(Employee employee) {
throw new UnsupportedOperationException();
}
}
4.2 单元测试
上面的两个类代码都非常简单,但是写一个专门针对 EmployeeService 的单元测试类,你会发现运行根本不能通过
public class EmployeeServiceTest {
@Test
public void testGetEmployeeCount() {
final EmployeeService employeeService = new EmployeeService();
int count = employeeService.getEmployeeCount();
assertEquals(10, count);
}
@Test
public void testCreateEmployee() {
final EmployeeService employeeService = new EmployeeService();
Employee employee = new Employee();
employeeService.createEmployee(employee);
assertTrue(true);
}
}
4.3 使用 Mock
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeUtils.class)
public class EmployeeServiceTest {
@Test
public void testGetEmployeeCountWithMock() {
PowerMockito.mockStatic(EmployeeUtils.class);
PowerMockito.when(EmployeeUtils.getEmployeeCount()).thenReturn(10);
final EmployeeService employeeService = new EmployeeService();
int count = employeeService.getEmployeeCount();
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();
}
}
运行上述的单元测试,你就会得到正确的测试结果和预期,首先我们让 PowerMock在运行之前准备 EmployeeUtils 类,并且我们采用了 mockstatic 的方法,其中这个有别于 mock 非静态的类,然后其他的操作就会完全一样了
5. Verifying
Verifying 是一个非常强大的测试工具,不仅在 powermock 中使用,就连 easymock,
mockito 等框架都会使用,他的目的是为了检查某个被测试的方法是否顺利的被调用了
5.1 使用场景
假设我们有一个这样的业务接口,传递一个 Employee 实例参数,然后通过 DAO 查询该 Employee 是否在对应的数据库中存在,如果不存在则创建一个 Employee 数据,否则就更新原来的 Employee
5.2 业务代码
EmployeeDao.java 代码如下所示
public class EmployeeDao {
/**
* @param employee
*/
public void saveEmployee(Employee employee) {
throw new UnsupportedOperationException();
}
/**
* @param employee
*/
public void updateEmploye(Employee employee) {
throw new UnsupportedOperationException();
}
/**
* @param employee
* @return
*/
public long getCount(Employee employee) {
throw new UnsupportedOperationException();
}
}
同样为了模拟 DAO 比较难以测试,我们依旧抛出了运行时异常,好了,再来看一看Service 中是如何调用的吧
public class EmployeeService {
public void saveOrUpdate(Employee employee) {
final EmployeeDao employeeDao = new EmployeeDao();
long count = employeeDao.getCount(employee);
if (count > 0)
employeeDao.updateEmployee(employee);
else
employeeDao.saveEmployee(employee);
}
}
在开始编写测试代码之前,我们先来思考一下,其中 Service 中的 saveOrUpdate 方法是用了 EmployeeDao,并且作为一个局部变量被实例化,因此我们必须采用 PowerMock的方法进行测试,但是最重要的是由于其中多了一层逻辑判断,这给我们的断言带来了一定的难度,由于不能真正的连接数据库,所以没有办法产生真实的数据,所以我们断言的依据是什么?是新增了一条数据还是更改了一条记录呢?
显然采用传统的方式我们更本没有办法做到这一点,但是 Verifying 却可以让我们很容易的应对这样一个场景,首先我们不难看出,当 count 大于 0 的时候我们就需要执行更新操作,当 count 小于等于 0 的时候我们就需要执行新增的操作,那么我们就可以通过验证DAO 中所涉及的方法是否被调用来进行验证了,如果这么说您还是不能理解,接着看下面的两个测试用例
5.3 测试代码
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class)
public class EmployServiceTest {
@Test
public void testSaveOrUpdateCountLessZero() {
try {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
PowerMockito.whenNew(EmployeeDao.class).withNoArguments().thenReturn(employeeDao);
Employee employee = new Employee();
PowerMockito.when(employeeDao.getCount(employee)).thenReturn(0L);
EmployeeService employeeService = new EmployeeService();
employeeService.saveOrUpdate(employee);
Mockito.verify(employeeDao).saveEmployee(employee);
Mockito.verify(employeeDao, Mockito.never()).updateEmploye(employee);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
@Test
public void testSaveOrUpdateCountMoreThanZero() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
try {
PowerMockito.whenNew(EmployeeDao.class).withNoArguments().thenReturn(employeeDao);
Employee employee = new Employee();
PowerMockito.when(employeeDao.getCount(employee)).thenReturn(1L);
EmployeeService employeeService = new EmployeeService();
employeeService.saveOrUpdate(employee);
Mockito.verify(employeeDao).updateEmploye(employee);
Mockito.verify(employeeDao, Mockito.never()).saveEmployee(employee);
} catch (Exception e) {
fail();
}
}
}
verify 方法的使用,我们根据 getCount 的返回的结果去验证对应的方法是否被调用了,从而来测试我们的 service 是否逻辑运行正确当然 Verify 的所涉及的方法还不至这些,其中有很多的 verification modes
5.4 Verifying 其他 API
Mockito.verify(employeeDao, Mockito.never()).saveEmployee(employee);
Mockito.verify(employeeDao,Mockito.atLeastOnce()).saveEmployee(employee);
Mockito.verify(employeeDao,Mockito.times(1)).saveEmployee(employee);
Mockito.verify(employeeDao,Mockito.atMost(1)).saveEmployee(employee);
Mockito.verify(employeeDao,Mockito.atLeast(1)).saveEmployee(employee);
6. Mock final
是使用 PowerMock 测试一下某个 final 修饰的 class 或 者 method,但是为了更加有说服力,本章会引入 easymock 作为对比, 看看 easymock 是否能够 mock 一个 final修饰的class 或者 method
6.1 业务代码
其中业务代码和之前的业务代码没有什么不一样,都是一个 service 调用其中的一个dao,但是这个 dao class 是被 final 修饰的,也就是说他是不能被继承的,我们看一下代
码吧
Service 代码
public class EmployeeService {
private EmployeeDao employeeDao;
public EmployeeService(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public void createEmployee(Employee employee) {
employeeDao.insertEmployee(employee);
}
}
Dao 的代码如下
final public class EmployeeDao {
public boolean insertEmployee(Employee employee) {
throw new UnsupportedOperationException();
}
}
Service 代码没有什么,重点看一下 DAO 的代码,修饰符多了一个 final 类型,同时为
了能够方便 EasyMock 进行测试,我将 EmployeeDao 在 EmployeeService 中通过了构造方法传入(因为 EasyMock 不能在 mock 方法内部的变量)
6.2 EasyMock 测试
public class EmployeeServiceEasyMockTest {
@Test
public void test() {
EmployeeDao employeeDao = EasyMock.createMock(EmployeeDao.class);
Employee employee = new Employee();
EasyMock.expect(employeeDao.insertEmployee(employee)).andReturn(true);
EasyMock.replay(employeeDao);
EmployeeService employeeService = new EmployeeService(employeeDao);
employeeService.createEmployee(employee);
EasyMock.verify(employeeDao);
}
}
执行上面的单元测试,你会发现运行出现了错误,但是当您 将 EmployeeDao 的 final 修饰符去 掉之后就会运 行成功,由此 可见EasyMock 是通过代理的方式实现(继承代理)产生一个新的 Mock 对象的,同样 final 修饰的方法也会不适合 EasyMock 的场景
6.3 PowerMock 测试
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeDao.class)
public class EmployeeServicePowerMockTest {
@Test
public void test() {
EmployeeDao employeeDao = PowerMockito.mock(EmployeeDao.class);
Employee employee = new Employee();
PowerMockito.when(employeeDao.insertEmployee(employee)).thenReturn(true);
EmployeeService employeeService = new EmployeeService(employeeDao);
employeeService.createEmployee(employee);
Mockito.verify(employeeDao).insertEmployee(employee);
}
}
7. Mock constructors
有些时候我们需要在类的构造函数中传递某些参数进去,这也是非常常见的一种编码习惯,这个时候我们应该如何去 Mock 这样的类呢?
7.1 使用场景
举个例子,DAO 的构造需要传递两个参数,其中一个是 boolean 类型标识是否懒加载,另外一个是 enumeration 类型,标识采用何种数据库方言,当然有可能构造该 DAO 需要很重的资源,并且在我们的单元测试环境下不一定能够很容易的获得,因此需要 mock
7.2 业务代码
我们先来看看 DAO 的代码
public class EmployeeDao {
public enum Dialect {
MYSQL, ORACLE
}
public EmployeeDao(boolean lazy, Dialect dialect) {
throw new UnsupportedOperationException();
}
public void insertEmploye(Employee employee) {
throw new UnsupportedOperationException();
}
}
再来看看 Service 的代码
public class EmployeeService {
public void createEmployee(final Employee employee) {
EmployeeDao employeeDao = new EmployeeDao(false, Dialect.MYSQL);
employeeDao.insertEmploye(employee);
}
}
7.3 PowerMock 测试
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class)
public class EmployeeServiceTest {
@Test
public void test() {
EmployeeDao employeeDao = mock(EmployeeDao.class);// (1)
try {
whenNew(EmployeeDao.class).withArguments(false, MYSQL).thenReturn(employeeDao);// (2)
EmployeeService employeeService = new EmployeeService();
Employee employee = new Employee();
employeeService.createEmployee(employee);
Mockito.verify(employeeDao).insertEmploye(employee);// (3)
} catch (Exception e) {
fail();
}
}
}
(1)我们首先 mock 了一个 EmployeeDao 实例
(2)我们用 whenNew 语法,并且委以相关的参数,注意这里的参数必须和 Service中的参数一致,否则在 Service 中还会继续创建一个新的 EmployeeDao 实例。
(3)我们验证了方法的调用。
7.4 whenNew 语法
我们在上面的 PowerMock 测试用例中看到了一个新的语法那就是 whenNew,其实我们在前面的章节中都有涉猎该语法,这个语法的意思是当碰到 new 这个关键字时,返回某个被 Mock 的对象而不是让真的 new 行为发生,回想我们在 Service 中 new 一个新的EmployeeDao,如果我们不提前准备这个 new 行为,那么他会在运行期创建一个新的EmployeeDao 实例,此时我们 Mock 出来的实例将会不起任何作用的,并且对应的参数一定要一致,如果您不能确保参数的一致性,那就是用 withAnyArguments,当然还有对应的很多 whenNew 语法,我们来一起看一下。
8. Arguments Matcher
Arguments Matcher 是一个比较强大的 Mock 接口,其实这并不是 PowerMock 的原创,其实在 EasyMock,Mockito 中均有相关的支持,同时它也是一个比较灵活的 Mock手段
8.1 使用场景
有些时候我们 Mock 一个对象的时候,需要根据不同的参数返回不同的内容,虽然Mock 的对象是假的,但是我们还是希望能够这样做,做一些临界值的测试,并且得到相关的预期结果,好了,我们先来看看业务代码,其中业务代码也比较简单,我们从最基本的Controller 开始,然后通过 Controller 调用相应的 Service。
8.2 业务代码
Controller 的代码
public class EmployeeController {
public String getEmail(final String userName) {
EmployeeService employeeService = new EmployeeService();
String email = employeeService.findEmailByUserName(userName);
return email;
}
}
Service 的代码如下
public class EmployeeService {
public String findEmailByUserName(String userName) {
throw new UnsupportedOperationException();
}
}
根据以前的知识,我们只能 Mock 一个 Service,并且能够预先设定一个返回结果,但是我们在编写代码的时候,提前会根据输入的参数获得不同的返回值,比如参数为 Null 或者一个不存在的结果等等。总而言之,我们就像根据参数的不同而获得不同的返回结果。
8.3 PowerMock 测试
public class EmployeeControllerTest {
@Test
public void testGetEmail() {
EmployeeService employeeService = mock(EmployeeService.class);
when(employeeService.findEmailByUserName(Mockito.argThat(newArgumentMatcher<String>(){
@Override
public boolean matches(Object argument) {
String arg = (String)argument;
if(arg.startsWith("xx")||arg.startsWith("xxx"))
return true;
else
throw new RuntimeException();
}
}))).thenReturn("xx@gmail.com");
try {
whenNew(EmployeeService.class).withAnyArguments().thenReturn(employeeService);
EmployeeController controller = new EmployeeController();
String email = controller.getEmail("xxx");
assertEquals("xx@gmail.com",email);
controller.getEmail("liudehua");
fail("should not process to here.");
} catch (Exception e) {
assertTrue(e instanceof RuntimeException);
}
}
}
上面的这个 PowerMock 测试代码,我们通过实现一个匿名 ArgumentMatcher 类,然后就实现了根据不同参数获得不同的返回结果预期,这样我们就可以少些很多when…thenReturn
9. Answer interface
其实 Answer 接口的作用和 Arguments Matcher 比较类似,但是它比 Arguments Matcher 更加强大,我们还是根据上一章节中的那个示例然后来说明 Answer 的使用方法。
9.1 使用场景
使用场景在这里还是罗嗦一下吧,当用户名是 xx或者 xxx的时候能够返回xx@gmail.com , 当 用 户 名 是 liudehua 或 者 ldh 的 时 候 返 回 的 是andy@gmail.com,如果用户名是其他则抛出无法找到的异常。
9.2 业务代码
Service 代码
public class EmployeeService {
public String findEmailByUserName(String userName) {
throw new UnsupportedOperationException();
}
}
Controller 代码
public class EmployeeController {
public String getEmail(final String userName) {
EmployeeService employeeService = new EmployeeService();
String email = employeeService.findEmailByUserName(userName);
return email;
}
}
9.3 PowerMock 测试
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeController.class)
public class EmployeeControllerTest {
@Test
public void testGetEmail() {
EmployeeService employeeService = PowerMockito.mock(EmployeeService.class);
PowerMockito.when(employeeService.findEmailByUserName(Mockito.anyString())).then(new Answer<String>(){
@Override
public String answer(InvocationOnMock invocation) throws
Throwable {
String argument = (String) invocation.getArguments()[0];
if("xx".equals(argument))
return "xx@gmail.com";
else if("liudehua".equals(argument))
return "andy@gmail.com";
throw new NullPointerException();
}
});
try {
PowerMockito.whenNew(EmployeeService.class).withNoArguments().thenRe
turn(employeeService);
EmployeeController controller = new EmployeeController();
String email = controller.getEmail("xx");
assertEquals("xx@gmail.com",email);
email = controller.getEmail("liudehua");
assertEquals("andy@gmail.com",email);
email = controller.getEmail("JackChen");
fail("should not process to here.");
} catch (Exception e) {
assertTrue(e instanceof NullPointerException);
}
}
}
9.4 answer 接口中参数 InvocationOnMock
invocation.getArguments();(1)
invocation.callRealMethod();(2)
invocation.getMethod();(3)
invocation.getMock();(4)
(1)获取 mock 方法中传递的入参
(2)获取是那个真实的方法调用了该 mock 接口
(3)获取是那么 mock 方法被调用了
(4)获取被 mock 之后的对象
10. Mocking with spies
10.1 使用场景
如果某个对象是 mock 产生的,那么他什么都不会做,除非你对其做了when…thenReturn 这样的操作,否则所mock的对象调用任何方法,什么都不会做的,如果您没有做过类似的测试,我们在这里一起来看看。
public class FileService {
public void write(final String text) {
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(System.getProperty("user.dir") + "/xx.txt"));
bw.write(text);
bw.flush();
System.out.println("content write successfully.");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bw != null)
try {
bw.close();
} catch (IOException e) {
// be quietly
}
}
}
}
上面的代码其实完全不可以用 mock 的方式来测试,但是为了说明我刚才的理论,我们来写一个测试用例来看一看
public class FileServiceTest {
@Test
public void testWrite() {
FileService fileService = PowerMockito.mock(FileService.class);
fileService.write("liudehua");
}
}
运行上面的测试用例,你会发现根本没有生成一个 xx.txt 文件,也就意味着被 mock 的 class 是个彻头彻尾的假对象,什么都干不了的。
10.2 PowerMock 测试
我们采用 spy 的方式 mock 一个对象,然后再调用其中的某个方法,他就会根据真实
class 的所提供的方法来调用,好了,我们再来写一个 spy 的测试
@Test
public void testWriteSpy() {
FileService fileService = PowerMockito.spy(new FileService());
fileService.write("liudehua");
File file = new File(System.getProperty("user.dir") + "/xx.txt");
assertTrue(file.exists());
}
运行上面的测试用例,你会发现生成了 xx.txt 文件,并且里面有 liudehua字样。
10.3 何时使用 Spy
Spy 是一个特别有意思的 API,他能让你 mock 一个对象,并且只 mock 个别方法的行为,保留对某些方法原始的业务逻辑,根据具体情况选择使用。
11. Mocking private methods
单纯就测试 private 修饰的方法而言,这个非常有争议,有人说测试 private 方法采用反射的方式会破坏 class 的封装性,有人说既然是单元测试需要面面俱到,在互联网上争论
都是比较激烈的,那方都没有说服另外一方,我个人也是比较赞成不要通过反射的方式去测试一个私有方法,如果私有方法写得好,对调用处的代码写好单元测试就会完全覆盖私有方法的逻辑。
但是本章中所要体现出来的场景还真的需要去 mock 一个 private 方法,好了,来看看
最后一个 PowerMock 的功能吧。
11.1 使用场景
假设我们有一个类,其中有一个方法 A 是公有方法,但是他需要依赖一个私有方法 B,但是其中的方法 B 是一个很难在单元测试中真实模拟,所以我们需要 mock 私有方法行
为,好了我们同样看一看业务代码,然后再通过 PowerMock 的方式来进行一下测试
11.2 业务代码
public class EmployeeService {
public boolean exist(String userName) {
checkExist(userName);
return true;
}
private void checkExist(String userName) {
throw new UnsupportedOperationException();
}
}
11.3 PowerMock 测试
上面的业务代码中我们发现如果要测试 exist 方法,肯定需要实现 mock 出来checkExist 的行为,否则真的没有办法对 exist 进行测试。
@RunWith(PowerMockRunner.class)
@PrepareForTest(EmployeeService.class)
public class EmployeeServiceTest {
@Test
public void testExist() {
try {
EmployeeService employeeService = PowerMockito.spy(new EmployeeService());
PowerMockito.doNothing().when(employeeService, "checkExist", "xx");
boolean result = employeeService.exist("xx");
assertTrue(result);
PowerMockito.verifyPrivate(employeeService).invoke("checkExist","xx");
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}