3.springboot单元测试

不忘初心才能始终 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...
3.springboot单元测试
这三个依赖是支持静态类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.没有静态方法调用

3.springboot单元测试
要测的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");

但是如上面的这个调用会产生类似:
3.springboot单元测试
的错误,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层的测试一定程度上可以交由测试进行测试,单元测试如果要覆盖到每一个判断分支也是不好写的,比较痛苦。编写测试一定程度上可以发现代码错误,可以借此重构代码。

相关推荐