Practice test unit (SpringCloud + Junit5 + Mockito + DataMocker)

Online read one sentence, like unit testing early hours, everyone says is good, but few people do it. Project from so many years of personal experience proved to be true.
Through this opportunity to implement unit tests in the project, recording the process of implementation and some lessons learned.

Project Status

The first is the background, the project is a larger project, more teamwork development, using as a basis SpringCloud micro-services architecture, middleware involving Redis, MySQL, MQ, and so on. Started a new starting point, the team expect to be able to discuss the use of unit testing to improve code quality. The advantages of unit testing a lot, but I think the ultimate goal is the ultimate quality, unit test code if it is not able to improve the quality of projects, indicating that the process is not really a problem or team accepted method, it is better to give up to save you development time.
When it comes to unit testing I am sure you will first think of TDD. TDD (Test Dirven Development, Test Driven Development) is a unit testing methodology to drive development.

  1. Develop a new feature before, start by writing unit test cases
  2. Running unit tests, all failed (red)
  3. Write business code, and the test unit through a corresponding (green)
  4. Time maintain your unit tests, it is always run

A team to start the direct implementation possibility for TDD is relatively small, because the development process for the team, the bottom frame package testing, unit testing principles and are not yet finalized specification or worked out best practice. Directly start full implementation, the process tends to be deformed, the ultimate goal will slowly on track, the whole team is not willing to accept a unit test. It is proposed to start gradually, so that the team can personally experience the benefits of unit testing and then slowly overweight.

Our project-based technical architecture is based on SpringCloud, do some basic underlying package. Calls between projects based on Feign, various projects are standardized to provide their Feign interface and Hystrix the FallbackFactory. We will for external calls are encapsulated in the service of the underlying.

Unit test range

A project need to implement unit testing, we must first define the scope (or clarification) responsible for the unit test. The most common confusion is associated with external systems, or other middleware, unit testing whether the actual call to other middleware / external systems.
Let's take a look at the definition of unit testing:

Unit tests are typically automated tests written and run by software developers to ensure that a section of an application (known as the "unit") meets its design and behaves as intended.

Unit testing should first be automated, written by the developer, in order to ensure that the code fragment (minimum unit) is implemented on the intended design. We understand That is to protect the unit test item (snippet logic) itself performs properly as intended, it was confirmed that the test unit is limited to a range within a single item, so try to block all of the middleware or external systems. Business logic code cover 80% -90%, the rest of (tools etc.) is not required.
Our project involves a number of middleware (Mysql, Redis, MQ, etc.), but more related to other internal support system. Unit test coverage range of the actual situation in our current project is defined, as an inlet unit testing the controller, and the controller to try to cover all service logic and methods, all external interface calls all the mock, middleware to make use of the intermediate memory piece conduct mock.

Test unit base frame

Since the project is based on SpringCloud, it will certainly be a test based on the introduction of spring-boot-test, select the underlying testing framework is junit.
Junit mainstream or junit4 ( Github address ) the latest version is 4.12 (5 December 2014), and now the latest is junit5 (JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage). junit5 official version of the release date is September 11, 2017, the latest version is 5.5.2 (September 9, 2019). We project the underlying chose junit5.
At present, the main camp in Java Mock test tools Mockito, JMock, EasyMock and so on. We chose Mockito, this is not the result of special selection. Select a relatively easy to use and able to meet the current needs after a simple comparison.
redis uses redis-mock (ai.grakn: redis- mock: 0.1.6)
database is to use natural h2 (com.h2database: h2: 1.4.192) ( but in a major project of our service orchestration, not related to database examples of)
simulated data generating reference jmockdata (com.github.jsonzou: jmockdata: 4.1.2) , but some small adjustments made to increase a number of other types of

In addition, mock Mockito does not support the static method to use to simulate PowerMock. But now it seems PowerMock not support junit5, we did not use.

Unit Test Example

The basic framework set up is completed, the basic entered the coding phase. The first phase of coding, we are in fact still the first to write a business code, and write unit tests. Then I explain in detail the structure of the unit test classes. The example given here is only that we have to use in practice, not junit5 complete notes or use to explain the specific need to know we can refer to the official website .

The basic structure of the test unit

Take a look at a few notes of the head, these are the Junit5

// 替换了Junit4中的RunWith和Rule
@ExtendWith(SpringExtension.class)
//提供spring依赖注入
@SpringBootTest 
// 运行单元测试时显示的名称
@DisplayName("Test MerchantController")
// 单元测试时基于的配置文件
@TestPropertySource(locations = "classpath:ut-bootstrap.yml")
class MerchantControllerTest{
    private static RedisServer server = null;

    // 下面三个mock对象是由spring提供的
    @Resource
    MockHttpServletRequest request;

    @Resource
    MockHttpSession session;

    @Resource
    MockHttpServletResponse response;

    // junit4中 @BeforeClass
    @BeforeAll
    static void initAll() throws IOException {
        server = RedisServer.newRedisServer(9379); 
        server.start();
    }


    // junit4中@Before
    @BeforeEach
    void init() {
        request.addHeader("token", "test_token");
    }

    // junit4中@After
    @AfterEach
    void tearDown() {
    }

    // junit4中@AfterClass
    @AfterAll
    static void tearDownAll() {
        server.stop();
        server = null;
    }

}

These are the basis of comparison of notes, and also basic junit4 correspondence. There is not much to say, you can see we load the virtual redis server initialization method, set the value of the Header in the pre-method

Method of testing body unit

Our main test is MerchantController this class, this class below a layer of service method. Look at the code probably impression.

    @Resource
    MerchantController merchantController;

    @MockBean
    private IOrderClient orderClient;

    @Test
    void getStoreInfoById() {
        MockConfig mockConfig = new MockConfig();
        mockConfig.setEnabledCircle(true);
        mockConfig.sizeRange(2, 5);
        MerchantOrderQueryVO merchantOrderQueryVO = Mock.mock(MerchantOrderQueryVO.class);
        StoreInfoDTO storeInfoDTO = Mock.mock(StoreInfoDTO.class,mockConfig);

        Mockito.when(orderClient.bizInfoV3(Mockito.any())).thenReturn(R.data(storeInfoDTO));
        Mockito.when(orderClient.getOrderCount(Mockito.any())).thenReturn(R.data(merchantOrderQueryVO));

        R<StoreInfoBizVO> r = merchantController.getStoreInfoById();

        assertEquals(r.getData().getAvailableOrderCount(), merchantOrderQueryVO.getOrderNum());
        assertEquals(r.getData().getId(), storeInfoDTO.getId());
        assertEquals(r.getData().getBranchName(), storeInfoDTO.getBranchName());
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 0})
    void logoutCheck(Integer onlineValue) {
        MockConfig mockConfig = new MockConfig();
        mockConfig.setEnabledCircle(true);
        mockConfig.sizeRange(2, 5);
        MerchantOrderQueryVO merchantOrderQueryVO = Mock.mock(MerchantOrderQueryVO.class);
        StoreInfoDTO storeInfoDTO = Mock.mock(StoreInfoDTO.class,mockConfig);
        storeInfoDTO.setOnline(onlineValue);
        Mockito.when(orderClient.bizInfoV3(Mockito.any())).thenReturn(R.data(storeInfoDTO));
        Mockito.when(orderClient.getOrderCount(Mockito.any())).thenReturn(R.data(merchantOrderQueryVO));

        R r = merchantController.logoutCheck();

        if (1==onlineValue) {
            assertEquals(ResourceAccessor.getResourceMessage(
                    MerchantbizConstant.USER_LOGOUT_CHECK_ONLINE), r.getMsg());
        } else {
            assertEquals(ResourceAccessor.getResourceMessage(
                    MerchantbizConstant.USER_LOGOUT_CHECK_UNCOMPLETED), r.getMsg());
        }
    }

    @ParameterizedTest
    @CsvSource({"1,Selma,true", "2,Lisa,true", "3,Tim,false"})
    void forTest(int id,String name,boolean t) {
        System.out.println("id="+id+" name="+name+" tORf="+t);
        merchantController.forTest(null);
    }

First look at the variable part, here to give two examples, a comment is @Resource, this spring is to be injected. Another is @MockBean, which is provided Mockito, and binds Mockito.when following method.
Next, look at the method body, I will be the main method is divided into three parts:

  1. Mock data and methods
    used to intercept the bottom of the external interface Mock method of Mock and return random data (much of the data may be used to generate DataMocker, some special limited, it can be generated manually).
  2. Test method for performing
    execution target test method (basically a line directly call the target method and returns the result)
  3. The results assertions
    were based on business logic written assertion expected (this part is basically no automated way, because the conditions and business logic assertions related only manually written)

Write down the basic logic verification, as well as internal branching logic, how to verify?
Indeed amongst the codes also mentioned, is @ParameterizedTest annotations junit5 provided with @ValueSource, @CsvSource used, respectively, may be provided to specify the type or complex type in the test unit, receiving method parameters, define different test branch.

Unit test execution

Execution unit test is divided into two parts:

  1. IDE, we're going to verify that unit tests can be executed successfully
  2. CI / CD as a prerequisite for the implementation of safeguards

IDE test frame can be specified directly, we directly choose junit5 test code generation unit, on the right can be performed directly in the test unit tests the class or package. This method can be used as a means to test the effectiveness of the treatment of our development process verification. But really want to be better in the production development process reflects the value of unit testing, continuous integration still need the support of our project using jenkins. Dependence is Maven, and the maven-surefire-plugin plugin. Pay special attention to that, because junit5 still relatively new, so the maven-surefire-plugin plugin support junit5 still a little bit special, refer to the official website explained . We need to introduce plug-in:

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M3</version>
            <configuration>
                <excludes>
                    <exclude>some test to exclude here</exclude>
                </excludes>
            </configuration>
        </plugin>

This will execute unit tests when building jenkins, if the unit fails the test, will not trigger post-build operation (Post Steps).

to sum up

At present our project, unit testing application is still the first period, but the investment of time and effort on it, in fact, to 2-3 times the actual development time. Since the introduction of integration involves basic framework to build a new framework underlying the development of the preparation of the audit, the team's training test code and so on. I expect that in the late mature framework and process support, covering the core business of the code should be able to unit test takes about 50% -80% of the actual development work. But investment in this part of the test and is able to reduce the probability of occurrence of a problem online, saving repair time.
The team is still not completely used to the rhythm of unit tests, now under the direct benefits of obvious enough, but a good habit, still need to invest energy managers from both the top-down driven.
For the latter should perform unit testing and some adjustments or improvements, and its terms of concepts, processes, etc. we should also have more in-depth and practical understanding. We will also organize again, and to others.

Guess you like

Origin www.cnblogs.com/pluto4596/p/11703382.html