3.springboot单元测试
因为公司单元测试覆盖率需要达到80%,所以进行单元测试用例编写。多模块项目的因为会经常调用其他服务,而且避免数据库操作对于数据库造成影响,所以所有的操作都要mock掉,也就是模拟调用的结果。测试用例编写可以参考https://www.journaldev.com/21...,上面介绍也比较详细,不过以下介绍是自己遇到的一些问题。
3.1配置文件
6.14.3
4.12
2.19.0
2.0.0-beta.5
org.springframework.boot
spring-boot-starter-test
test
org.testng
testng
${testng.version}
test
junit
junit
${junit4.version}
test
org.mockito
mockito-core
${mockito-core.version}
test
org.powermock
powermock-module-testng
${powermock.version}
test
org.powermock
powermock-module-junit4
${powermock.version}
test
org.powermock
powermock-api-mockito2
${powermock.version}
test
可以参考如https://www.journaldev.com/21...
这三个依赖是支持静态类mock所需要的,还有一个版本的问题,之前
java.lang.NoSuchMethodError: org.mockito.internal.handler.MockHandlerFactory.createMockHandler(Lorg/mockito/mock/MockCreationSettings;)Lorg/mockito/internal/InternalMockHandler;
的报错,修改了版本后来得到了解决。
3.2.没有静态方法调用
要测的serverImpl类使用@InjectMocks,其他所有的依赖的类使用@Mock掉,这样可以模拟依赖的类的实现而不是调用依赖类的实现,放置比如调用数据库操作导致的问题。类上使用@RunWith(SpringRunner.class)注解。
一下为测试代码,withLearningService.getGift(passId, code, token, ip)里面会用到paramUtil.getParamByParamName(PRODUCT_CODE)以及openApiService.userQueryInfoById(anyString()),所以我们需要把用到的两个方法模拟它的返回结果。
when(paramUtil.getParamByParamName(PRODUCT_CODE)).thenReturn(productCode);
when(openApiService.userQueryInfoById(anyString())).thenReturn(intevee);
withLearningService.getGift(passId, code, token, ip);
使用when模拟paramUtil的返回结果,也可以使用any()模拟对象,anyString()来字符串,还有anyInt(),anyLong()模拟其他类型参数。测试的时候可以把每个判断分支都走到。
如果类加上@SpringBootTest注解的话项目会启动springboot项目加载各种配置,速度会慢很多,建议直接都mock掉不需要启动springboot环境。
3.3需要使用静态方法
类上使用
@RunWith(PowerMockRunner.class)
@PrepareForTest({ HttpsUtils.class, RedisUtil.class, SpringContextUtil.class,AESUtil.class,KeyGenerator.class })
两个注解,需要使用静态方法的类添加到@PrepareForTest的参数里面。一般的静态方法调用直接使用:
PowerMockito.mockStatic(RedisUtil.class);
PowerMockito.when(RedisUtil.get(anyString())).thenReturn("0");
但是如上面的这个调用会产生类似:
的错误,RedisUtil是我司自己封装的Redis操作工具类,查看RedisUtil.get方法,看到需要注入spring的上下文环境ApplicationContext applicationContext。所以需要添加该上下文环境
PowerMockito.mockStatic(SpringContextUtil.class);
PowerMockito.when(SpringContextUtil.getApplicationContext()).thenReturn(new XmlWebApplicationContext());
PowerMockito.mockStatic(RedisUtil.class);
PowerMockito.when(RedisUtil.get(anyString())).thenReturn("0");
这样可以解决问题,我自己编写的过程中遇到了一个加解密类报错,工具类代码如下
if (key == null || content == null || content.isEmpty()) {
return null;
}
String msg = null;
byte[] byteRresult = new byte[content.length() / 2];
for (int i = 0; i < content.length() / 2; i++) {
int high = Integer.parseInt(content.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(content.substring(i * 2 + 1, i * 2 + 2), 16);
byteRresult[i] = (byte) (high * 16 + low);
}
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] result = cipher.doFinal(byteRresult);
return new String(result);
测试类的mock编写如入下:
PowerMockito.mockStatic(AESUtil.class);
PowerMockito.when(AESUtil.decrypt(anyString(), anyString())).thenReturn("11");
这样编写报错,改为以下则完成测试用例编写。
PowerMockito.mockStatic(KeyGenerator.class);
KeyGenerator keyg = Mockito.mock(KeyGenerator.class);
PowerMockito.when(KeyGenerator.getInstance(anyString())).thenReturn(keyg);
PowerMockito.mockStatic(AESUtil.class);
PowerMockito.when(AESUtil.decrypt(anyString(), anyString())).thenReturn("11");
同KeyGenerator keyg = Mockito.mock(KeyGenerator.class);这句代码,可以通过如此mock一个对象,使用以上方法基本上可以编写所有代码的测试类。这里的测试最好都是针对server层进行测试,为什么不对controller层也进行测试,其实主要是单元测试的意义是把基本的逻辑跑通,controller层的测试一定程度上可以交由测试进行测试,单元测试如果要覆盖到每一个判断分支也是不好写的,比较痛苦。编写测试一定程度上可以发现代码错误,可以借此重构代码。