In today’s article, I intend to talk about the topic of testing with you. Maybe some friends will ask, as a coder, why should I pay attention to the problem of testing? We’ve developed the code and we’re done with basic self-testing, so why don’t we just throw it to the test? Have a problem to change again! It is true that many programmers in China do not pay much attention to Unit Test at present, and many Internet companies do not require developers to write Unit Test cases. The reason may be that domestic companies are relatively rich, and there are many companies with dozens or even hundreds of people in the testing team. So, from the mentality of many programmers, there are so many tests, just throw them to test! Another reason mentioned is that domestic Internet companies have too fast product iteration speed and too many demands, so they can’t do it. Where do they have time to write Unit Test?

There may be a variety of reasons, but aside from the various factors, today we will discuss whether we should write Unit Test from the perspective of programmer growth. Recently, I have cooperated with overseas programmer friends to develop many projects. I feel that they put special emphasis on Unit Test. In their words, they pay more attention to the quality of programs. On the contrary, many domestic companies do this is not so good! I talked with them about the reason before, and they thought that the development of China in recent years was too fast, so that some processes were skipped.

We know that when developing a piece of software or developing a requirement in an existing project, we generally go through a strict process, as shown in the following figure:

As can be seen from the figure, the software must pass Unit Test before being delivered to integration Test in this process. However, many domestic companies bypass Unit Test and directly transition to integration Test and QA Test in the testing process. Objectively speaking, developers usually know the most about logic. If the development can be done by covering a relatively complete Unit Test, the subsequent testing process will actually be much smoother, and writing Unit Test also has the advantage of encouraging developers to constantly optimize the design logic of the code, because once you find that the code can’t be used by Unit Test, Your code is not componentized enough and needs to be refactored! As a programmer, if you can constantly review your code during this process, you will improve your code writing skills.

From the perspective of software maintainability, comprehensive projects covered by Unit Test are often easier to maintain, because the complete Unit Test has actually solidified the current logic of the software. Once someone breaks this logic in subsequent development, Unit Test will fail. If code that cannot be run by Unit Test cannot compile or commit, the modifier will be forced to improve Unit Test. This also improves programmer testing awareness from the side and reduces the chance of major bugs!

From both points of view, Unit Test can improve the programmer’s coding skills and ensure the quality of the software as much as possible, so it is a very valuable thing to do. No wonder they say that good programmers spend 20% of their time writing Unit Test!

Unit Test

In the previous content, we said that Unit Test is a very valuable thing, so how should Unit Test be written in the actual project? Taking a Web service developed using the Spring Boot framework based on Spring MVC as an example, the code structure in most cases looks like this:

In this software structure, the service interface definition of the Controller layer is generally invoked externally, which is supported by the Spring MVC framework. After receiving the request, the Controller layer needs to pass the parameters to the business method of the Service layer for processing, and the business method logic of the Service layer will be more diverse. For example, it may need to operate the database through the components provided by the Dao layer, or it may need to access several middleware components. Such as Redis, a caching service, RocketMQ, a messaging service, and so on. In addition, the Service layer logic may also involve the invocation of other third-party services, for example, the payment business also needs to invoke the interface of Alipay and so on!

Generally speaking, Unit Test focuses on the business logic methods of the Service layer. If the Controller layer also involves some process logic, it needs to be covered by Unit Test. The specific Unit Test use cases should be written in accordance with Maven project specifications.

As shown in the figure above, the Service layer itself relies on many other components. Some of them need to call the database, some need to access Redis, and some need to call third-party interfaces. In this case, it seems difficult to keep Unit Test running, because it is impossible to keep the environment online every time you run Unit Test. What can you do? So in the early days of writing A Unit Test, we need to write the Mock Test code manually if there are third-party dependencies that cannot be tested. For example, suppose we have A business layer class class A{… } needs to be tested by Unit, but A relies on A third party component B. Since B needs to connect to an external network, we cannot directly rely on an instance of B when testing A. Therefore, we generally need to define A separate class MockB extend B{@override… }, this class inherits B and mocks its methods to provide Mock beans for the Unit Test of class A! This complexity of component dependencies limits your enthusiasm for writing Unit Tests to some extent, but this artifact will make it much easier!

Unit Test Mockito

In the previous section, we talked about how complex component dependencies required us to write additional Mock classes when writing the business layer Unit Test, and Mockito made it very easy to Mock. Mockito is a Mock testing framework that allows us to elegantly Mock component-dependent mocks and validate execution logic in the form of annotations (@MockBean). The general steps for using Mockito are as follows:

  1. Emulate any external third-party component dependencies and plug these mock objects into the test code;
  2. Execute the code under test;
  3. Verify that the code performs as expected;

If we had introduced the test dependency Jar in our Spring Boot project, we would have introduced the Junit and Mockito test framework dependencies. As follows:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
</dependency>
Copy the code

Here we use a practical case to demonstrate how to write a code for the Service layer Unit Test, Service business logic code is as follows:

@Service public class UserAccountTradeServiceImpl implements UserAccountTradeService { @Autowired WalletOrderDao walletOrderDao; @Autowired PaymentClient paymentClient; @Override public AccountChargeTradeResVo accountChargeTrade(AccountChargeTradeReqVo accountChargeTradeReqVo) throws Exception {/ / top-up trading against the heavy WalletOrder WalletOrder = walletOrderDao. SelectOrderById (accountChargeTradeReqVo. GetOrderId ());if(walletOrder ! = null) { throw new Exception("Duplicate top-up order"); } / / build walletOrder = walletOrder top-up order. The builder (). The orderId (accountChargeTradeReqVo. GetOrderId ()) .userId(String.valueOf(accountChargeTradeReqVo.getUserId())) .amount(accountChargeTradeReqVo.getAmount()) .busiType("0").tradeType("charge").currency(accountChargeTradeReqVo.getCurrency()).status("1") .isRenew(accountChargeTradeReqVo.getReNew()).tradeTime(new Timestamp(new Date().getTime())) .updateTime(new Timestamp(new Date().getTime())) .build(); walletOrderDao.insertOrder(walletOrder); / / call the payment interface paymentClient. ConsumeAccount (1,"1"."CNY"); / / build return parameter AccountChargeTradeResVo AccountChargeTradeResVo = AccountChargeTradeResVo. Builder () .userId(Long.valueOf(walletOrder.getUserId())).currency(walletOrder.getCurrency()) .orderId(walletOrder.getOrderId()).businessType(walletOrder.getBusiType()).build();returnaccountChargeTradeResVo; }}Copy the code

The above business code is actually a business-layer method that illustrates the general logic of a user’s wallet top-up, and there are two dependency components that need to be Mock: one is walletOrderDao, which represents the operation on the database, and the other is paymentClient, which represents the client that needs to call the payment system. How do you Mock a Unit Test using Mockito?

In the package structure of the test directory corresponding to the project, we established a test code structure that is the same as the logical package structure of the business layer, as shown in the figure below:

Generally speaking, the code interface of the Unit Test class is the same as the actual source code structure. It is ok to name the class under Test with the suffix +Test. Next we write the test code:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {UserAccountTradeServiceImpl.class})
//@ActiveProfiles({"test"})
public class UserAccountTradeServiceImplTest {

    @MockBean
    private WalletOrderDao walletOrderDao;

    @MockBean
    private PaymentClient paymentClient;

    @Autowired
    private UserAccountTradeServiceImpl userAccountTradeServiceImpl;

    @Test
    public void accountChargeTradeTest() throws Exception {
        AccountChargeTradeReqVo accountChargeTradeReqVo = AccountChargeTradeReqVo.builder().orderId("12345")
                .userId(1001).amount(1000).currency("CNY").tradeTime("2019070412102023").reNew("1").build(); AccountChargeTradeResVo accountChargeTradeResVo = userAccountTradeServiceImpl .accountChargeTrade(accountChargeTradeReqVo); assertNotNull(accountChargeTradeResVo); assertEquals(accountChargeTradeResVo.getOrderId(), accountChargeTradeReqVo.getOrderId()); given(paymentClient.consumeAccount(any(Long.class), any(String.class), any(String.class))).willReturn(null); verify(paymentClient).consumeAccount(any(Long.class), any(String.class), any(String.class)); }}Copy the code

In the above test code, we easily Mock the dependent component of the business layer code with the @MockBean annotation, so that the test code will be Mock when executing the dependent component logic without actually calling the method. In general, we can verify that a Mock object is invoked, but only to verify that the invocation itself is triggered, using the given/ Verify methods provided by Mocktio.

For the most part, using this pattern for Unit tests is fine. For more details, check out Mocktio! There is also a small trick in this example that we use with @Springboottest as follows:

@SpringBootTest(classes = {UserAccountTradeServiceImpl.class})
Copy the code

You can specify the Service class to Test directly so that Spring Boot doesn’t load other messy dependencies, saving Unit Test time.

The main purpose of this article is to form a good habit of writing Unit Test, and to be an excellent programmer who pays attention to code quality. I hope everyone can become more and more excellent, come on!