1, Background
Many companies have certain requirements for branch single test coverage. For example, the single test coverage must reach 60% or 80% before it can be released.
Sometimes the construction period is relatively tight, so we give priority to developing functions and testing functions, and then supplement unit testing.
However, writing unit tests is a waste of time. Is there a plug-in that can automatically generate unit tests to a large extent and simply change it yourself?
I tried to search for relevant plug-ins in the Idea plug-in library and try to use them. I found that TestMe was OK. Later, I communicated with other students. My partner recommended the Squaretest he had been using. After I tried it, I found it quite good.
These two plug-ins are briefly introduced here.
If you try other plug-ins related to unit testing first, you can click here or in the IDEA: https://plugins.jetbrains.com/search?orderBy=downloads&tags=Unit%20testing
2, Recommended tools
2.1 Squaretest
2.1.1 introduction to use
Official website address: https://squaretest.com/ Official user manual: https://squaretest.com/#user_guide Official website plug-in address https://plugins.jetbrains.com/plugin/10405-squaretest
Advantages: the generated code is relatively regular, the generated code is relatively, and it helps to construct some parameters. Disadvantages: when the Confirm Mock function is not used, when generating single test code for Spring beans, if the attribute is injected through @ Setter annotation, the @ Mock attribute will not be generated; If you want to implement it, you can only modify the template to support it for the time being (which will be given later).
usage method: You can select the first one in the [square test] menu on the top menu or use the corresponding shortcut key to create a unit test.
Generated code:
This example is relatively simple. It just shows you how to use it. When the class is complex in actual use, you can realize the power of the plug-in.
Example code:
import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service public class UserManager { @Resource private UserDAO userDAO; public List<UserDO> someThing(Param param) { List<UserDO> result = new ArrayList<>(); 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()); } }
Generated code:
import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class UserManagerTest { @Mock private UserDAO mockUserDAO; @InjectMocks private UserManager userManagerUnderTest; @Test public void testSomeThing() { // Setup final Param param = new Param(); param.setUserIds(Arrays.asList("value")); param.setOthers("others"); // Configure UserDAO.findByIds(...). final UserDO userDO = new UserDO(); userDO.setCanShow(false); userDO.setName("name"); final List<UserDO> userDOS = Arrays.asList(userDO); when(mockUserDAO.findByIds(Arrays.asList("value"))).thenReturn(userDOS); // Run the test final List<UserDO> result = userManagerUnderTest.someThing(param); // Verify the results } @Test public void testSomeThing_UserDAOReturnsNoItems() { // Setup final Param param = new Param(); param.setUserIds(Arrays.asList("value")); param.setOthers("others"); when(mockUserDAO.findByIds(Arrays.asList("value"))).thenReturn(Collections.emptyList()); // Run the test final List<UserDO> result = userManagerUnderTest.someThing(param); // Verify the results assertEquals(Collections.emptyList(), result); } }
Official Demo 1: selective generation of test code
Official Demo 2: select the attribute that needs mock
Official example 3: write test in a single test to select the method to be tested and automatically generate test code
2.2.2 customization
As for the default template mentioned earlier, when generating single test code for Spring beans, if the beans are declared in xml and the attributes are injected through @ Setter annotation, the @ Mock attribute will not be generated.
public class UserManager { @Setter private UserDAO userDAO; public List<UserDO> someThing(Param param) { // ellipsis } }
You can use the Confirm Mocks function to select this attribute
The plug-in also supports the adjustment of the generated template:
You can also make simple modifications to the template. All @ setters will be automatically annotated with @ Mock annotation:
Line 1526:
Add Setter annotation to the dependent annotation attribute.
## Add the simple names or cannonical names of any custom dependency annotations to the method call below. #set($dependencyAnnotatedFields = $sourceClass.fieldsAnnotatedWith('Inject', 'Setter','Autowired', 'Resource', 'PersistenceContext'))
If you use powermock, you need to modify lines 1502 - 1506:
#set($mockitoRunnerCanonicalName = 'org.powermock.modules.junit4.PowerMockRunner') #set($mockitoRunnerName = 'PowerMockRunner')
Delete part
#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 introduction to use
Plug in official website address https://plugins.jetbrains.com/plugin/9471-testme
Function:
Automatically generate Java JUnit 4 / 5 and TestNG unit tests Automatically generate Mockito mocks Automatically generate test parameters and assertion statements Automatic generation of correlation mock method IDEA menu: Code - > testme, code - > generate
Advantages: when Spring beans generate single test code, even if the annotation annotation of @ Component is used to inject attributes through Setter annotation, the attributes of @ Mock and @ InjectMock will be added automatically. Disadvantages: the default template will add throws Exception to the generated methods
Example code 1:
Or use the shortcut keys directly
Generated code:
This example is relatively simple. It just shows you how to use it. When the class is complex in actual use, you can realize the power of the plug-in.
Example code 2:
import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service public class UserManager { @Resource private UserDAO userDAO; public List<UserDO> someThing(Param param) { List<UserDO> result = new ArrayList<>(); 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()); } }
Generated code
import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.List; import static org.mockito.Mockito.*; public class UserManagerTest { @Mock UserDAO userDAO; @InjectMocks UserManager userManager; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testSomeThing() throws Exception { when(userDAO.findByIds(any())).thenReturn(Arrays.<UserDO>asList(new UserDO())); List<UserDO> result = userManager.someThing(new Param()); Assert.assertEquals(Arrays.<UserDO>asList(new UserDO()), result); } } //Generated with love by TestMe :) Please report issues and submit feature requests at: http://weirddev.com/forum#!/testme
You can simply modify it on this basis.
2.2.2 customization
You can also modify the template according to your needs:
There are several problems with the default template: 1. The @ RunWith(MockitoJUnitRunner.class) annotation is not added on the class 2. The unit test method will be followed by throws Exception by default, which is not necessary 3. Bottom testme footer Java content does not need 4. There is no empty line between @ Mock and @ InjectMock
Modify a copy of junit4 & mockito (the original file is read-only)
Generate a copy of junit4 & mockito template, which can be modified
Modified template:
#parse("Copy of TestMe macros.java") #set($hasMocks=$MockitoMockBuilder.hasMockable($TESTED_CLASS.fields)) #if($PACKAGE_NAME) package ${PACKAGE_NAME}; #end import static org.junit.Assert.*; import org.junit.Test; #if($hasMocks) import org.junit.Before; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; import org.junit.Assert; //import static org.mockito.Mockito.*; #end #parse("File Header.java") @RunWith(MockitoJUnitRunner.class) public class ${CLASS_NAME} { #renderMockedFields($TESTED_CLASS.fields) #renderTestSubjectInit($TESTED_CLASS,$TestSubjectUtils.hasTestableInstanceMethod($TESTED_CLASS.methods),$hasMocks) #if($hasMocks) @Before public void setUp() { } #end #foreach($method in $TESTED_CLASS.methods) #if($TestSubjectUtils.shouldBeTested($method)) @Test public void #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 }
Then we select this template when generating unit tests:
It is found that the generated code format is much better:
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.List; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class UserManagerTest { @Mock UserDAO userDAO; @InjectMocks UserManager userManager; @Before public void setUp() { } @Test public void testSomeThing() { when(userDAO.findByIds(any())).thenReturn(Arrays.<UserDO>asList(new UserDO())); List<UserDO> result = userManager.someThing(new Param()); assertEquals(Arrays.<UserDO>asList(new UserDO()), result); } }
3, Single test efficient construction of parameter and return value artifact
We can also use other tools to automatically generate test parameters or return values. https://github.com/j-easy/easy-random
You can refer to my previous article: Artifact of efficient object construction in Java: an introduction to easy random
One or two lines can construct a very complex object or object list.
Artifact of generating test string from Java unit test: Java faker
If we want to randomly construct people's names, place names, weather, schools, colors, occupations, and even strings that match a regular expression
4, Summary
Flexible use of unit tests to automatically generate plug-ins can save a lot of time.
You can install and try these two plug-ins, and then choose the plug-in that is most suitable for you according to your preferences. You can also adjust the template according to your preferences. In addition, we should not be too demanding on plug-ins. The generated unit tests still need to be modified more or less, such as modifying parameters and adding several assertions.