不忘初心才能始终 2019-07-01
3.springboot单元测试
因为公司单元测试覆盖率需要达到80%,所以进行单元测试用例编写。多模块项目的因为会经常调用其他服务,而且避免数据库操作对于数据库造成影响,所以所有的操作都要mock掉,也就是模拟调用的结果。测试用例编写可以参考https://www.journaldev.com/21...,上面介绍也比较详细,不过以下介绍是自己遇到的一些问题。
3.1配置文件
<properties> <testng.version>6.14.3</testng.version> <junit4.version>4.12</junit4.version> <mockito-core.version>2.19.0</mockito-core.version> <powermock.version>2.0.0-beta.5</powermock.version> </properties> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- TestNG --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <!-- JUnit 4 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit4.version}</version> <scope>test</scope> </dependency> <!-- Mockito 2 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>${mockito-core.version}</version> <scope>test</scope> </dependency> <!-- PowerMock TestNG Module --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-testng</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <!-- PowerMock JUnit 4.4+ Module --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> <!-- PowerMock Mockito2 API --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency>
可以参考如https://www.journaldev.com/21...
这三个依赖是支持静态类mock所需要的,还有一个版本的问题,之前<powermock.version>2.0.0-beta.5</powermock.version>的版本是<powermock.version>1.7.5</powermock.version>遇到了类似
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层的测试一定程度上可以交由测试进行测试,单元测试如果要覆盖到每一个判断分支也是不好写的,比较痛苦。编写测试一定程度上可以发现代码错误,可以借此重构代码。