一、背景
很多公司对分支单测覆盖率会有一定的要求,比如 单测覆盖率要达到 60% 或者 80%才可以发布。
有时候工期相对紧张,就优先开发功能,测试功能,然后再去补单元测试。
但是编写单元测试又比较浪费时间,有没有能够很大程度上自动化生成单元测试的插件,自己简单改改即可呢?
自己尝试在 Idea 插件库里搜索相关插件并去尝试使用,发现
TestMe
还可以。后面和其他同学交流,谎伴 同学推荐他一直在用的
Squaretest
,我试用之后发现相当不错。
在这里简单介绍这两个插件。
如果先尝试其他单元测试相关插件,可以在 IDEA 里 或者点这里:
https://plugins.jetbrains.com/search?orderBy=downloads&tags=Unit%20testing
二、推荐工具
2.1 Squaretest
2.1.1 使用介绍
官网地址:https://squaretest.com/
官方用户手册:https://squaretest.com/#user_guide
官网插件地址
https://plugins.jetbrains.com/plugin/10405-squaretest
优点:生成的代码比较规整,生成的代码比较,帮助构造一些参数等。
缺点:不使用 Confirm Mock功能时,对Spring 的 Bean 生成单测代码时,如果属性是通过 @Setter 注解注入,则不会生成 @Mock 属性 ;如果想实现暂时只能自己修改模板来支持(后面会给出)。
使用方法:
可以在顶部菜单 [Squaretest] 菜单中选择第一个或者使用对应快捷键创建单元测试。
生成的代码:
这个例子比较简单,只是给大家演示如何使用,实际使用中类复杂时,就能体会到该插件的强大。
示例代码:
importorg.apache.commons.collections4.CollectionUtils;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.Collectors;@ServicepublicclassUserManager{@ResourceprivateUserDAO userDAO;publicList<UserDO>someThing(Param param){List<UserDO> result =newArrayList<>();if(param ==null){return result;}List<String> userIds = param.getUserIds();if(CollectionUtils.isEmpty(userIds)){return result;}List<UserDO> users = userDAO.findByIds(userIds);if(CollectionUtils.isEmpty(users)){return result;}return users.stream().filter(UserDO::getCanShow).collect(Collectors.toList());}}
生成的代码:
importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.junit.MockitoJUnitRunner;importjava.util.Arrays;importjava.util.Collections;importjava.util.List;importstaticorg.junit.Assert.assertEquals;importstaticorg.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class)publicclassUserManagerTest{@MockprivateUserDAO mockUserDAO;@InjectMocksprivateUserManager userManagerUnderTest;@TestpublicvoidtestSomeThing(){// SetupfinalParam param =newParam();
param.setUserIds(Arrays.asList("value"));
param.setOthers("others");// Configure UserDAO.findByIds(...).finalUserDO userDO =newUserDO();
userDO.setCanShow(false);
userDO.setName("name");finalList<UserDO> userDOS =Arrays.asList(userDO);when(mockUserDAO.findByIds(Arrays.asList("value"))).thenReturn(userDOS);// Run the testfinalList<UserDO> result = userManagerUnderTest.someThing(param);// Verify the results}@TestpublicvoidtestSomeThing_UserDAOReturnsNoItems(){// SetupfinalParam param =newParam();
param.setUserIds(Arrays.asList("value"));
param.setOthers("others");when(mockUserDAO.findByIds(Arrays.asList("value"))).thenReturn(Collections.emptyList());// Run the testfinalList<UserDO> result = userManagerUnderTest.someThing(param);// Verify the resultsassertEquals(Collections.emptyList(), result);}}
官方演示1:选择性生成测试代码
官方演示2:选择需要 mock 的属性
官方示例3:在单测里写 test 即可选择需要测试的方法自动生成测试代码
2.2.2 定制化
前面讲到默认的模板,对Spring 的 Bean 生成单测代码时,如果通过 xml 方式声明 bean ,属性都是通过 @Setter 注解注入,则不会生成 @Mock 属性 。
publicclassUserManager{@SetterprivateUserDAO userDAO;publicList<UserDO>someThing(Param param){// 省略}}
可以使用 Confirm Mocks 功能选择该属性需要 Mock
该插件也支持对生成的模板进行调整:
还可以对模板进行简单修改,所有 @Setter 都会自动加上 @Mock 注解:
1526 行:
在依赖的注解属性中添加
Setter
注解即可。
## Add the simple names or cannonical names of any custom dependency annotations tothe method call below.
#set($dependencyAnnotatedFields = $sourceClass.fieldsAnnotatedWith('Inject','Setter','Autowired','Resource','PersistenceContext'))
如果使用 powermock ,需要进行修改 1502 -1506 行:
#set($mockitoRunnerCanonicalName ='org.powermock.modules.junit4.PowerMockRunner')
#set($mockitoRunnerName ='PowerMockRunner')
删除 部分
#if(!$ClassUtils.isInTestClasspath('org.mockito.junit.MockitoJUnitRunner')&& $ClassUtils.isInTestClasspath('org.mockito.runners.MockitoJUnitRunner'))
#set($mockitoRunnerCanonicalName ='org.mockito.runners.MockitoJUnitRunner')
#end
2.2 TestMe
2.2.1 使用介绍
插件官网地址
https://plugins.jetbrains.com/plugin/9471-testme
功能:
自动生成 Java JUnit 4/5, TestNG 单元测试
自动生成 Mockito mocks
自动生成 测试参数和断言语句
自动生成相关 mock 方法
IDEA 菜单: Code->TestMe, Code->Generate
优点:Spring 的 Bean 生成单测代码时,即使 @Component 这类注解标注,属性通过 Setter 注解注入时,也会自动给添加 @Mock 和 @InjectMock 这类属性。
缺点:默认模板会在生成的方法上都加上 throws Exception
示例代码1:
或者直接使用快捷键
生成的代码:
这个例子比较简单,只是给大家演示如何使用,实际使用中类复杂时,就能体会到该插件的强大。
示例代码2:
importorg.apache.commons.collections4.CollectionUtils;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.Collectors;@ServicepublicclassUserManager{@ResourceprivateUserDAO userDAO;publicList<UserDO>someThing(Param param){List<UserDO> result =newArrayList<>();if(param ==null){return result;}List<String> userIds = param.getUserIds();if(CollectionUtils.isEmpty(userIds)){return result;}List<UserDO> users = userDAO.findByIds(userIds);if(CollectionUtils.isEmpty(users)){return result;}return users.stream().filter(UserDO::getCanShow).collect(Collectors.toList());}}
生成的代码
importorg.junit.Assert;importorg.junit.Before;importorg.junit.Test;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importjava.util.Arrays;importjava.util.List;importstaticorg.mockito.Mockito.*;publicclassUserManagerTest{@MockUserDAO userDAO;@InjectMocksUserManager userManager;@BeforepublicvoidsetUp(){MockitoAnnotations.initMocks(this);}@TestpublicvoidtestSomeThing()throwsException{when(userDAO.findByIds(any())).thenReturn(Arrays.<UserDO>asList(newUserDO()));List<UserDO> result = userManager.someThing(newParam());Assert.assertEquals(Arrays.<UserDO>asList(newUserDO()), result);}}//Generated with love by TestMe :) Please report issues and submit feature requests at: http://weirddev.com/forum#!/testme
自己在此基础上简单修改即可。
2.2.2 定制化
大家还可以根据自己需要对模板进行修改:
默认模板存在几个问题:
1、没有在类上增加
@RunWith(MockitoJUnitRunner.class)
注解
2、单元测试方法后面默认会带上 throws Exception 没有太大必要
3、底部 TestMe Footer.java 的内容不需要
4、@Mock 和 @InjectMock 之间没空行
对 Junit4 & mockito 复制一份(原始文件是只读的)进行修改
生成一个 Copy of Junit4 & mockito 的模板,可以对其进行修改
修改后的模板:
#parse("Copy of TestMe macros.java")
#set($hasMocks=$MockitoMockBuilder.hasMockable($TESTED_CLASS.fields))
#if($PACKAGE_NAME)package ${PACKAGE_NAME};
#end
importstaticorg.junit.Assert.*;importorg.junit.Test;
#if($hasMocks)importorg.junit.Before;importorg.junit.runner.RunWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importorg.mockito.junit.MockitoJUnitRunner;importorg.junit.Assert;//import static org.mockito.Mockito.*;
#end
#parse("File Header.java")@RunWith(MockitoJUnitRunner.class)publicclass ${CLASS_NAME}{
#renderMockedFields($TESTED_CLASS.fields)
#renderTestSubjectInit($TESTED_CLASS,$TestSubjectUtils.hasTestableInstanceMethod($TESTED_CLASS.methods),$hasMocks)
#if($hasMocks)@BeforepublicvoidsetUp(){}
#end
#foreach($method in $TESTED_CLASS.methods)
#if($TestSubjectUtils.shouldBeTested($method))@Testpublicvoid #renderTestMethodName($method.name)(){
#if($MockitoMockBuilder.shouldStub($method,$TESTED_CLASS.fields))
#renderMockStubs($method,$TESTED_CLASS.fields)
#end
#renderMethodCall($method,$TESTED_CLASS.name)
#if($method.hasReturn()) #renderJUnitAssert($method)#end
}
#end
#end
}
然后我们生成单元测试时选择该模板:
发现生成的代码格式好了不少:
importorg.junit.Before;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.junit.MockitoJUnitRunner;importjava.util.Arrays;importjava.util.List;importstaticorg.junit.Assert.assertEquals;importstaticorg.mockito.Mockito.*;@RunWith(MockitoJUnitRunner.class)publicclassUserManagerTest{@MockUserDAO userDAO;@InjectMocksUserManager userManager;@BeforepublicvoidsetUp(){}@TestpublicvoidtestSomeThing(){when(userDAO.findByIds(any())).thenReturn(Arrays.<UserDO>asList(newUserDO()));List<UserDO> result = userManager.someThing(newParam());assertEquals(Arrays.<UserDO>asList(newUserDO()), result);}}
三、单测高效构造参数和返回值神器
我们还可以借助其他工具,自动生成测试的参数或者返回值。
https://github.com/j-easy/easy-random
可以参考我之前的一篇文章:
《Java高效构造对象的神器:easy-random 简介》
一两行就可以构造一个非常复杂的对象或者对象列表。
《Java 单元测试生成测试字符串的神器:java-faker》
如果我们想要随机构造人名、地名、天气、学校、颜色、职业,甚至符合某正则表达式的字符串
四、总结
灵活使用单元测试自动生成插件,可以节省很多时间。
大家可以安装并试用这两个插件,然后根据自己的喜好,选择最适合自己的那个插件使用。
也可以根据自己的喜好,对模板进行调整。
此外,大家不要对插件要求太高,生成的单元测试或多或少还是需要自己进行简单修改,如修改下参数、增加几个断言等。
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
版权归原作者 明明如月学长 所有, 如有侵权,请联系我们删除。