• 一款自动生成单元测试的 IDEA 插件,开发效率提升 70% 以上!


    今天来介绍一款工具Squaretest,它是一款自动生成单元测试的插件,为什么会用到它?

    主要因为最近公司上了代码质量管控的指标,会考评各个项目的单元测试覆盖率,以及sonar扫描出来的各种问题,很多老项目老代码,或者着急交付的项目,单元测试严重缺失,覆盖率只有5%不到。

    所以几个小伙伴这几天就在疯狂的堆单元测试,3个人堆了2天才堆到30%,于是我也来上手帮忙写了两个,写到第二个的时候就发现,这个活不应该是人干的,要去看原来的代码,然后根据逻辑写各种Mock,感觉是有迹可循的东西,所以就查了下,发现果然有插件帮我们来干这个事情,那么解下来就来看看。

    另外,更多 IDEA 好用的插件全部整理好了,微信搜索Java技术栈,在后台发送:工具,可以在线阅读。

    我使用的是idea,我们先来下载一下插件,File——>Settings——>Plugins,搜索Squaretest,然后install就好了,插件安装完成后需要重启一下

    重启之后,菜单栏就多了一项Squaretest,下面我们来讲下怎么用,大家也可以通过看这个菜单的最后一项:Generate Test Methods(Help)来看它的一个演示,但演示不太全,我下面截图给大家看下我怎么用的,以及一些使用心得。

    首先我们打开一个类,这个类就是我们即将要作为实验的类,这个类有7个public方法,因为Squaretest生成的单元测试方法都是只能生成public的,当然这也是合理的嘛!毕竟private的肯定被public调用了。

    如果我们来手写这个类的单元测试,光看都要一会,下面看我操作,打开你的类,光标定位到代码里,右击鼠标选择Generate…

    然后你就会看到这里有两个熟悉的图标,第一次的话选择第二个选项,它会让你选择你一下单元测试的模板,因为我已经选择过了,所以我现在演示不回再弹出,但后面我会告诉你怎么更改模板。

    选择第二项后就会弹出一个框看下面这里它自动会识别出当前类需要Mock的成员变量,直接点ok

    自动会使用类的真实目录层次在test文件夹中创建出来一个单元测试类,类名就是原类名后加Test

    我把代码贴出来给大家看看它生成出来的是什么样的,看看吓不吓人,牛逼牛逼,7个单元测试方法,秒秒钟就出来了,各位看官你们自己写要多久能写出来,毕竟时间就是金钱啊!然后我们执行一把试试!

    public class CrawlerScreenShotServiceImplTest {
    
        @Mock
        private CrawerScreenShotTaskMapper mockCrawerScreenShotTaskMapper;
        @Mock
        private CrawerScreenShotTaskLogMapper mockCrawerScreenShotTaskLogMapper;
    
        @InjectMocks
        private CrawlerScreenShotServiceImpl crawlerScreenShotServiceImplUnderTest;
    
        @Before
        public void setUp() {
            initMocks(this);
        }
    
        @Test
        public void testReceiveData() {
            // Setup
            final CrawlerScreenShotVO vo = new CrawlerScreenShotVO();
            vo.setUrl("url");
            vo.setPcFlag(false);
            vo.setMembergroup("membergroup");
            vo.setTaskType(0);
            vo.setUrlType(0);
    
            when(mockCrawerScreenShotTaskLogMapper.saveSelective(any(CrawerScreenShotTaskLog.class))).thenReturn(0);
            when(mockCrawerScreenShotTaskMapper.saveBatch(Arrays.asList(new CrawlerScreenShotTask(0L, "url", "imageOssUrl", false, false, "memberGroup", 0, 0, "fileName", new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime(), new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime(), false, "skuCode", "state", "operater")))).thenReturn(0);
    
            // Run the test
            final Result<String> result = crawlerScreenShotServiceImplUnderTest.receiveData(vo);
    
            // Verify the results
        }
    
        @Test
        public void testListJobScreenShotTask() {
            // Setup
    
            // Configure CrawerScreenShotTaskMapper.listJobScreenShotTask(...).
            final CrawlerScreenShotTaskDto crawlerScreenShotTaskDto = new CrawlerScreenShotTaskDto();
            crawlerScreenShotTaskDto.setId(0L);
            crawlerScreenShotTaskDto.setUrl("url");
            crawlerScreenShotTaskDto.setSkuCode("skuCode");
            crawlerScreenShotTaskDto.setPcFlag(false);
            crawlerScreenShotTaskDto.setMemberGroup("memberGroup");
            crawlerScreenShotTaskDto.setUrlType(0);
            crawlerScreenShotTaskDto.setFileName("fileName");
            crawlerScreenShotTaskDto.setTaskType(0);
            crawlerScreenShotTaskDto.setState("state");
            final List<CrawlerScreenShotTaskDto> crawlerScreenShotTaskDtos = Arrays.asList(crawlerScreenShotTaskDto);
            when(mockCrawerScreenShotTaskMapper.listJobScreenShotTask(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime())).thenReturn(crawlerScreenShotTaskDtos);
    
            // Run the test
            final List<CrawlerScreenShotTaskDto> result = crawlerScreenShotServiceImplUnderTest.listJobScreenShotTask();
    
            // Verify the results
        }
    
        @Test
        public void testQuery() {
            // Setup
            final NikeScreenShotListRequestVo requestVo = new NikeScreenShotListRequestVo();
            requestVo.setUrl("url");
            requestVo.setUrlType(0);
            requestVo.setStartTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            requestVo.setEndTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            requestVo.setStatus(0);
            requestVo.setPcFlag(0);
            requestVo.setPageNum(0);
            requestVo.setPageSize(0);
    
            // Configure CrawerScreenShotTaskMapper.query(...).
            final PimScreenShotVo pimScreenShotVo = new PimScreenShotVo();
            pimScreenShotVo.setId(0L);
            pimScreenShotVo.setUrl("url");
            pimScreenShotVo.setImageOssUrl("imageOssUrl");
            pimScreenShotVo.setStatus(0);
            pimScreenShotVo.setPcFlag(false);
            pimScreenShotVo.setCreateTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            pimScreenShotVo.setUrlType(0);
            pimScreenShotVo.setMsg("msg");
            final List<PimScreenShotVo> pimScreenShotVos = Arrays.asList(pimScreenShotVo);
            when(mockCrawerScreenShotTaskMapper.query(any(NikeScreenShotListRequestVo.class))).thenReturn(pimScreenShotVos);
    
            // Run the test
            final PageInfo<PimScreenShotVo> result = crawlerScreenShotServiceImplUnderTest.query(requestVo);
    
            // Verify the results
        }
    
        @Test
        public void testQuerySelectBoxData() {
            // Setup
    
            // Configure CrawerScreenShotTaskMapper.query(...).
            final PimScreenShotVo pimScreenShotVo = new PimScreenShotVo();
            pimScreenShotVo.setId(0L);
            pimScreenShotVo.setUrl("url");
            pimScreenShotVo.setImageOssUrl("imageOssUrl");
            pimScreenShotVo.setStatus(0);
            pimScreenShotVo.setPcFlag(false);
            pimScreenShotVo.setCreateTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            pimScreenShotVo.setUrlType(0);
            pimScreenShotVo.setMsg("msg");
            final List<PimScreenShotVo> pimScreenShotVos = Arrays.asList(pimScreenShotVo);
            when(mockCrawerScreenShotTaskMapper.query(any(NikeScreenShotListRequestVo.class))).thenReturn(pimScreenShotVos);
    
            // Run the test
            final PimScreenShotTaskParamsDto result = crawlerScreenShotServiceImplUnderTest.querySelectBoxData();
    
            // Verify the results
        }
    
        @Test
        public void testFindExecutionScreenShotTaskCount() {
            // Setup
            when(mockCrawerScreenShotTaskMapper.findExecutionScreenShotTaskCount()).thenReturn(0);
    
            // Run the test
            final Integer result = crawlerScreenShotServiceImplUnderTest.findExecutionScreenShotTaskCount();
    
            // Verify the results
            assertEquals(0, result);
        }
    
        @Test
        public void testFindCrawerScreenshotTaskByCreateTime() {
            // Setup
            final CrawlerScreenShotTaskSyncDto crawlerScreenShotTaskSyncDto = new CrawlerScreenShotTaskSyncDto();
            crawlerScreenShotTaskSyncDto.setId(0L);
            crawlerScreenShotTaskSyncDto.setUrl("url");
            crawlerScreenShotTaskSyncDto.setSkuCode("skuCode");
            crawlerScreenShotTaskSyncDto.setTaskType(0);
            crawlerScreenShotTaskSyncDto.setStatus(0);
            crawlerScreenShotTaskSyncDto.setLastModifyTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            crawlerScreenShotTaskSyncDto.setOperater("operater");
            crawlerScreenShotTaskSyncDto.setMsg("msg");
            final List<CrawlerScreenShotTaskSyncDto> expectedResult = Arrays.asList(crawlerScreenShotTaskSyncDto);
    
            // Configure CrawerScreenShotTaskMapper.findCrawerScreenshotTaskByCreateTime(...).
            final CrawlerScreenShotTaskSyncDto crawlerScreenShotTaskSyncDto1 = new CrawlerScreenShotTaskSyncDto();
            crawlerScreenShotTaskSyncDto1.setId(0L);
            crawlerScreenShotTaskSyncDto1.setUrl("url");
            crawlerScreenShotTaskSyncDto1.setSkuCode("skuCode");
            crawlerScreenShotTaskSyncDto1.setTaskType(0);
            crawlerScreenShotTaskSyncDto1.setStatus(0);
            crawlerScreenShotTaskSyncDto1.setLastModifyTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
            crawlerScreenShotTaskSyncDto1.setOperater("operater");
            crawlerScreenShotTaskSyncDto1.setMsg("msg");
            final List<CrawlerScreenShotTaskSyncDto> crawlerScreenShotTaskSyncDtos = Arrays.asList(crawlerScreenShotTaskSyncDto1);
            when(mockCrawerScreenShotTaskMapper.findCrawerScreenshotTaskByCreateTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime())).thenReturn(crawlerScreenShotTaskSyncDtos);
    
            // Run the test
            final List<CrawlerScreenShotTaskSyncDto> result = crawlerScreenShotServiceImplUnderTest.findCrawerScreenshotTaskByCreateTime(new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
    
            // Verify the results
            assertEquals(expectedResult, result);
        }
    
        @Test
        public void testQueryCrawlerDashboard() {
            // Setup
            when(mockCrawerScreenShotTaskMapper.queryCrawlerDashboard(0, 0, 0, new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime(), new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime())).thenReturn(0);
    
            // Run the test
            final Integer result = crawlerScreenShotServiceImplUnderTest.queryCrawlerDashboard(0, 0, 0, new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime(), new GregorianCalendar(2019, Calendar.JANUARY, 1).getTime());
    
            // Verify the results
            assertEquals(0, result);
        }
    }
    

    报错了呢,不要慌,这个断言是为了检查你单元测试跑出来的结果是否符合预期的,如果你不想检查只想完成覆盖率,直接干掉就可以了(手动狗头)。

    怎么样!刺不刺激,爽不爽,秒秒钟90多行的代码覆盖率就到了90%以上.

    上面说过第一次进来会让你选择单元测试的模板,如果你要切换的话可以在单元测试类中按快捷键,Alt+M,或者通过Squaretest的菜单倒数第二个,下面这个就是按快捷键的效果,我选择的是这个模板,你们也可以借鉴。

    OK,以上Squaretest部分就结束了,当然拉也不能高兴的太早,这个类算是比较成功的情况,很多时候还是要你自己小修小改的,毕竟它生成出来的测试数据可能完全匹配不上你的if else数据对吧,但这都很好改啊,这样就从自己分析if else变成了,debug程序了呀,哪里报错,debug过去,看看是不是生成的数据有问题,改个数据,就通过了,反正本人用的是很舒畅的,妥妥的节省70%的工作量。

    解决了上面一个问题之后,又发现另一个问题,这个工具VO,DTO,Entity,Command,Model这种实体类来讲,一般这种实体类我们都用lombok的注解get,set,还有constract构造器等注解,但是这个工具只能生成这些实体类的构造器的单元测试,无法生成get set方法的单元测试,所以写了个base方法,实体类继承一下,简单的写两行带就好了,看下面代码:

    @SpringBootTest
    @RunWith(MockitoJUnitRunner.class)
    public abstract class BaseVoEntityTest<T> {
        protected abstract T getT();
    
        private void testGetAndSet() throws IllegalAccessException, InstantiationException, IntrospectionException,
                InvocationTargetException {
            T t = getT();
            Class modelClass = t.getClass();
            Object obj = modelClass.newInstance();
            Field[] fields = modelClass.getDeclaredFields();
            for (Field f : fields) {
                boolean isStatic = Modifier.isStatic(f.getModifiers());
                // 过滤字段
                if (f.getName().equals("isSerialVersionUID") || f.getName().equals("serialVersionUID") || isStatic || f.getGenericType().toString().equals("boolean")
                        || f.isSynthetic()) {
                    continue;
                }
                PropertyDescriptor pd = new PropertyDescriptor(f.getName(), modelClass);
                Method get = pd.getReadMethod();
                Method set = pd.getWriteMethod();
                set.invoke(obj, get.invoke(obj));
            }
        }
    
        @Test
        public void getAndSetTest() throws InvocationTargetException, IntrospectionException,
                InstantiationException, IllegalAccessException {
            this.testGetAndSet();
        }
    
    }
    

    同样的方式我们在实体类上通过Squaretest生成单元测试,然后继承我上面写的那个base类,vo的单元测试代码稍加改动,如下

    看run完之后,覆盖率100%,妥妥的,通过这两个解决方案,一天之内我们就把覆盖率搞到了60%以上,不要太刺激,大家可以用用试试哦,当然这个也不是纯为了应付差事写的单元测试,我们后续开发的时候,也可以用这个工具来生成,然后自测自己的代码,这样也是提升工作效率的嘛!

    觉得不错就用起来吧!

    版权声明:本文为CSDN博主「孙琛斌(浮生)」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/sun5769675/article/details/111043213

    近期热文推荐:

    1.1,000+ 道 Java面试题及答案整理(2022最新版)

    2.劲爆!Java 协程要来了。。。

    3.Spring Boot 2.x 教程,太全了!

    4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    【iOS开发-从网络上获取图片尺寸】
    【iOS开发之静态库、动态库】
    【iOS开发之OC和JS互调】
    【iOS之runtime、runloop】
    【iOS开发之C语言】sprintf,strncpy,strcmp三个函数的区别
    计算机中的存储单位
    linux命令行
    python的安装
    Java的跨平台特性
    方法的重写(override)两同两小一大原则:
  • 原文地址:https://www.cnblogs.com/javastack/p/16418291.html
Copyright © 2020-2023  润新知