3.springboot单元测试

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所需要的,还有一个版本的问题,之前2.0.0-beta.5的版本是1.7.5遇到了类似

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

你可能感兴趣的:(单元测试,springboot,java)