Java单元测试实践-00.目录(9万多字文档+700多测试示例)
1. Spring AOP与Mock
以下示例使用CGLIB代理,或JDK动态代理,执行结果相同。
以下使用注解的方式设置AOP,对方法或自定义注解设置AOP的效果相同。
后续内容在设置AOP,对方法设置了AOP。对自定义注解设置AOP的处理可参考示例TestSpAOPARawGet、TestSpAOPARun类。
1.1. 查看AOP代理对象信息
通过@Autowired等注解获得被AOP处理的对象时,获取到的对象为AOP代理对象,无法直接获取到原始对象。
- 代理对象类名
AOP代理对象不是原始类的实例。
当Spring AOP使用JDK动态代理时,AOP代理对象的类名示例如下:
com.sun.proxy.$Proxy36
当Spring AOP使用CGLIB代理时,AOP代理对象的类名示例如下:
com.adrninistrator.service.impl.TestAOPService1Impl$$EnhancerBySpringCGLIB$$1cb17163
当执行AOP代理对象的方法时,会先经过对应的Aspect的处理。
- 代理对象中的成员变量
当使用JDK动态代理时,由于代理对象中不包含成员变量,在获取代理对象中的成员变量时会出现异常,异常信息如下所示。
org.powermock.reflect.exceptions.FieldNotFoundException
No instance field of type "com.adrninistrator.service.TestService2" could be found in the class hierarchy of com.sun.proxy.$Proxy46.
当使用CGLIB代理时,获取代理对象中的成员变量,结果为空。
可参考示例TestSpAOPMProxyInfo1类。
1.2. 获取代理对象对应的原始对象
- 使用Advised.getTargetSource().getTarget()方法
当需要获取AOP代理对象对应的原始对象时,可以使用Advised.getTargetSource().getTarget()方法,示例如下:
@Autowired
private TestAOPService1 testAOPService1;
if (!AopUtils.isAopProxy(testAOPService1) || !(testAOPService1 instanceof Advised)) {
fail("illegal");
}
Advised advised = (Advised) testAOPService1;
TestAOPService1 testAOPService1Raw = (TestAOPService1) advised.getTargetSource().getTarget();
在获取到原始对象后,调用原始对应的方法时,不会经过对应的Aspect的处理。
可参考示例TestSpAOPMRawGet1类。
- 使用AopTestUtils.getTargetObject()方法
使用AopTestUtils.getTargetObject()方法也可以获取AOP代理对象对应的原始对象,相比使用Advised.getTargetSource().getTarget()方法更简单,示例如下:
@Autowired
private TestAOPService1 testAOPService1;
TestAOPService1 testAOPService1Raw2 = AopTestUtils.getTargetObject(testAOPService1);
使用Advised.getTargetSource().getTarget()方法与使用AopTestUtils.getTargetObject()方法获取原始对象的效果相同,获取到的为同一个对象。
可参考示例TestSpAOPMRawGet2类。
1.3. 将被引用的AOP代理对象替换为原始对象
通过上述方式获取AOP代理对象的原始对象后,可以将代理对象替换为原始对象,跳过Aspect的处理。示例如下,可参考示例TestSpAOPMRawGetUse1类。
@Autowired
private TestAOPService1 testAOPService1;
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Raw = AopTestUtils.getTargetObject(testAOPService1);
Whitebox.setInternalState(testAOPService2, testAOPService1Raw);
1.4. 将被引用的AOP代理对象替换为Mock对象
可将被测试类中的AOP代理对象替换为Mock对象,当被测试类调用Mock对象的方法时,不会经过对应的Aspect的处理,且可对Mock对象的方法进行Stub。示例如下,可参考示例TestSpAOPMProxyMock1类。
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Mock = Mockito.mock(TestAOPService1.class);
Mockito.when(testAOPService1Mock.testAround(Mockito.anyString())).thenReturn(TestConstants.MOCKED);
Whitebox.setInternalState(testAOPService2, testAOPService1Mock);
1.5. 将被引用的AOP代理对象替换为Spy对象
可将被测试类中的AOP代理对象替换为Spy对象,当被测试类调用Spy对象的方法时,不会经过对应的Aspect的处理,且可对Spy对象的方法进行Stub。示例如下,可参考示例TestSpAOPMProxySpy1类。
@Autowired
private TestAOPService1 testAOPService1;
@Autowired
private TestAOPService2 testAOPService2;
TestAOPService1 testAOPService1Raw = AopTestUtils.getTargetObject(testAOPService1);
TestAOPService1 testAOPService1Spy = Mockito.spy(testAOPService1Raw);
Mockito.doReturn(TestConstants.FLAG1).when(testAOPService1Spy).testAround(TestConstants.FLAG1);
Whitebox.setInternalState(testAOPService2, testAOPService1Spy);
1.6. 对Aspect进行Stub/Replace
在调用被AOP处理的类时,若需要跳过Aspect处理,可行方法除对AOP代理对象进行替换外,还可以对Aspect的方法通过PowerMockito.stub()、PowerMockito.replace()等方法进行Stub/Replace,改变Aspect的操作。可参考示例TestSpAOPMStubAspect1类。
1.7. 对原始对象进行Stub/Replace
在对AOP原始对象对应的类进行Stub/Replace时,不会对Aspect产生影响,Aspect仍生效。在调用AOP对象的方法时,仍会先经过Aspect的处理。可参考示例TestSpAOPMStubTarget1类。
1.8. 替换AOP原始对象中的成员变量
当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,不能对AOP代理对象进行成员变量替换。
当使用JDK动态代理时,由于代理对象中不包含成员变量,执行替换操作时会出现异常,异常信息如下所示。
org.powermock.reflect.exceptions.FieldNotFoundException
No instance field assignable from "com.adrninistrator.service.TestService2$MockitoMock$1618418885" could be found in the class hierarchy of com.sun.proxy.$Proxy46.
当使用CGLIB代理时,执行替换操作不会出现异常,但在执行AOP代理对象的方法时并不会调用被替换的成员变量。
可参考示例TestSpAOPMRawMockMemWrong1、TestSpAOPMRawSpyMemWrong1类。
当需要将被AOP处理的原始对象中的成员变量替换为Mock/Spy对象时,应当先获取AOP代理对象对应的原始对象,再将原始对象的成员变量替换为Mock/Spy对象。当直接或间接调用AOP代理对象的方法时( 其中调用了成员变量的方法 ),会调用成员变量的Mock/Spy对象对应的方法。示例如下:
@Autowired
private TestAOPService1 testAOPService1;
TestService2 testService2Mock = Mockito.mock(TestService2.class);
Mockito.when(testService2Mock.test1(TestConstants.FLAG3)).thenReturn(TestConstants.MOCKED);
TestAOPService1 testAOPService1Raw = AopTestUtils.getTargetObject(testAOPService1);
Whitebox.setInternalState(testAOPService1Raw, testService2Mock);
可参考示例TestSpAOPMRawMockMem1、TestSpAOPMRawSpyMem1类。
2. 对使用了事务的类进行Mock
对于声明使用事务的类,例如使用@Transactional注解等方式,其对应的对象会被处理为代理,通过@Autowired等注解获得使用事务的类的对象时,获取到的对象为代理对象。
获得使用事务的类的对象后,通过其Class对象,可以判断其为代理对象,说明可参考前文,可参考示例TestDatabaseTxInfo类。
以下所述的被测试代码,在事务中先后执行了数据库操作1与数据库操作2。
为了测试被测试代码使用了事务处理的数据库操作是否正确,当操作2出现异常时是否出现异常时是否会将操作1回滚,可对Mybatis的Mapper对象进行Mock并替换至被测试类中,对Mapper的Mock对象对象进行Stub,使其执行数据库操作1时调用原始Mapper对象的方法,执行操作2时抛出异常。通过以上处理,可以构造出使用事务的方法出现异常的场景,之后可以检查数据库记录是否符合预期。可参考示例TestDatabaseTxMockMem类。
当需要对使用事务的类的实例的成员变量进行替换时,需要获取代理对象对应的原始对象后再进行替换操作,可使用AopTestUtils.getTargetObject()方法,说明可参考前文,可参考示例TestDatabaseTxMockMem类,反例可参考示例TestDatabaseTxMockMemWrong类。
上述示例如下:
@Autowired
private TestTxService1 testTxService1;
@Autowired
private TestTableMapper testTableMapper;
TestTxService1 testTxService1Raw = AopTestUtils.getTargetObject(testTxService1);
TestTableMapper testTableMapperMock = TestReplaceUtil.replaceMockMember(testTxService1Raw, TestTableMapper.class);
Mockito.when(testTableMapperMock.insert(Mockito.any(TestTableEntity.class))).thenAnswer(invocation -> {
TestTableEntity arg1 = invocation.getArgument(0);
return testTableMapper.insert(arg1);
});
Mockito.when(testTableMapperMock.updateByPrimaryKeySelective(Mockito.any(TestTableEntity.class))).thenThrow
(new RuntimeException(TestConstants.MOCKED));
Mockito.when(testTableMapperMock.selectByPrimaryKey(Mockito.anyString())).thenAnswer(invocation -> {
String arg1 = invocation.getArgument(0);
return testTableMapper.selectByPrimaryKey(arg1);
});
3. 对使用了@Async注解的类进行Mock
对于被指定了@Async注解的方法,会以异步方式执行,对应类的对象会被处理为代理,通过@Autowired等注解获得使用了@Async注解的类的对象时,获取到的对象为代理对象。
为了使@Async注解生效,可以使用@EnableAsync注解,或在Spring的XML文件中通过task:annotation-driven进行配置,可通过executor参数指定线程池,通过proxy-target-class参数指定是否使用CGLIB代理(true:使用CGLIB代理,false:使用JDK动态代理,默认false),如下所示:
<task:annotation-driven executor="testThreadPoolTaskExecutor" proxy-target-class="true"/>
对使用了@Async注解的类进行Mock,与使用了AOP或事务的类处理类似,说明可参考前文,可参考示例TestAsyncRawGet、TestAsyncRun类。