Bidding farewell to overtime / freeing hands to improve single test coverage: Java automatic generation of single test code artifact recommendation

Posted by stuffradio on Tue, 15 Feb 2022 01:49:01 +0100

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.