Java SE foundation consolidation (XII): Unit Testing

1 Overview

As you may know, is a software development test a very important part, to verify whether the program is running in line with expectations (including the expected correctness, performance and quality procedures, etc.), if not in line with expectations, positioning on the report based on the results of the test problem, fix the problem, and then test again, a process that often requires repeated several times until the health program in line with expectations before they can attempt to publish, on the line, otherwise the product, the software is not responsible.

Depending on the classification, testing can be divided into different types, the most common and usually the most important thing is divided according to the stages of development, it can be divided into four main types of tests:

  • unit test
  • Integration Testing
  • System test
  • Acceptance Test

This article is the first major presentation of a: unit testing . As a developer, the other three may be less familiar, but the unit tests must be very familiar with.

Here is the definition from Wikipedia removal unit testing:

In computer programming , the unit test (English: Unit Testing), also known as module testing , is for the program modules ( software designed to test the work of verifying the correctness of the smallest units). It is the smallest program unit test component application. In procedural programming , the cell is a single program, functions, procedures, and the like; object-oriented programming, is the minimum unit, comprising a base class (superclass), abstract class or a derived class (subclass) method.

Can be said that the purpose of unit testing is to test the correctness of the program, the performance, availability is not required, it is often said that unit testing is the most basic test, if the unit can not pass the test, the latter test is not entirely necessary.

Java community there are many excellent third-party open-source testing framework, such as JUnit, Mockito, TestNG, etc. Now I will introduce the use of Junit and Mockito.

This article does not involve theoretical knowledge of software testing, only talks to the use of test tools.

2 JUnit

JUnit is a very well-known open-source testing framework, and even many non-Java developers are more or less heard. Junit now (2018-10-15) has released Junit5, a few more features, and a minimum supported Java version is Java8, but this article does not intend to use Junit5, instead of using JUnit4. Change About JUnit5 recommended to the official website to view.

2.1 download and install

Download the official website address is provided in the JUnit jar package, the package can be used to import jar. If the project is a Maven project, it can also be added to the pom.xml file junit dependency, as follows:

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
复制代码

JUnit 2.2 begun to taste

JUnit4 from the start, we can add @Test comment on the method to be tested to indicate that the method is a method to be tested. In JUnit3 time, in order to test a method that can only use the "naming scheme" method name to be tested methods provided in the form of testXXX named model has many shortcomings and deficiencies, so we recommend to make use of later versions JUnit4. Here is a simple use case of a JUnit4:

public class ApplicationTest {

    private int calculateSum(int a, int b) {
        return a + b;
    }

    @Test
    //这里的方法名只是一种习惯用法,JUnit4并不强制要求必须是testXXX
    public void testCalculate() {
        Assert.assertEquals(10, calculateSum(5, 5));       //通过
        Assert.assertEquals(10, calculateSum(20, -10));    //通过
        Assert.assertEquals(10, calculateSum(0,0));        //不通过,一般不会这样写,这里只是为了演示
        Assert.assertNotEquals(10, calculateSum(10, 10));  //通过
    }
}
复制代码

There @Test annotation methods are to be tested methods when the program starts, it will call all the methods to be tested, if an exception is thrown in the method, the method even if the test failed. Assert is a class under org.junit package, provides a rich API assertion for our use, for example assertEquals used to assert equal expectations and actual values, assertNull used to assert that the argument is a null value. In the case of the code, until only one test method, the test method is calculateSum target method, which assertions are four methods in order to verify whether the return value calculateSum expected, launcher, console output substantially as follows:


java.lang.AssertionError: 
Expected :10
Actual   :0
 <Click to see difference>


	at org.junit.Assert.fail(Assert.java:88)
	at org.junit.Assert.failNotEquals(Assert.java:834)
	at org.junit.Assert.assertEquals(Assert.java:645)
	at org.junit.Assert.assertEquals(Assert.java:631)
	at top.yeonon.ApplicationTest.testCalculate(ApplicationTest.java:21)
	.......
复制代码

You can see AssertionError method throws an exception and print exception stack for locating the problem, in addition, JUnit also gives a simple test report, namely:

java.lang.AssertionError: 
Expected :10
Actual   :0
复制代码

Expected That is the expected value, so that we in a program custom, Actual return value is calculateSum, JUnit wants to tell us is: Do you expect the value is 10, but the actual value is 0, that is not in line with expectations, should try fix the problem.

The following is an example of a relatively complex (and just above comparative example, the actual development is not so simple):

public class AppTest {

    @Test
    public void testAssertEqualAndNotEqual() {
        String name = "yeonon";
        Assert.assertEquals("yeonon", name);
        Assert.assertNotEquals("weiyanyu", name);
    }

    @Test
    public void testArrayEqual() {
        byte[] expected = "trial".getBytes();
        byte[] actual = "trial".getBytes();
        Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
    }

    @Test
    public void testBoolean() {
        Assert.assertTrue(true);
        Assert.assertFalse(false);
    }

    @Test
    public void testNull() {
        Assert.assertNull(null);
        Assert.assertNotNull(new Object());

    }

    @Test
    public void testThatHashItems() {
        Assert.assertThat(Arrays.asList("one","two","three"), CoreMatchers.hasItems("one","two"));
    }

    @Test
    public void testThatBoth() {
        Assert.assertThat("yeonon",
                CoreMatchers.both(
                        CoreMatchers.containsString("e"))
                        .and(CoreMatchers.containsString("o")));

    }
}

复制代码

In fact, try a variety of API Assert, not much to say, look at the method name probably know the function.

By the way, if you feel too much Assert and CoreMatchers looked tired, you can use static import package import packages, such as:

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
复制代码

JUnit usage is so simple and crude directly, which is why one of the reasons so popular JUnit. Of course, then the point is not the only function of JUnit, JUnit about more advanced features, it is recommended to JUnit official website to check out the official documents, it is good to write the document.

3 Mockito

Mockito is a very powerful testing framework, its greatest feature is the "Mock", namely simulation. A very important key point is to try unit test in the case does not involve the dependence of test code, try to simulate the real environment to do testing. Mockito can do this, he will be used as a packaging Mock objects, the Mock objects are configurable, that their behavior can be configured to look like we want.

For example, in a typical Web development, back-end will be divided into three layers, namely MVC, responsible for controlling the level of the students might have written the control layer, but the model layer is responsible for the students not to write, this time the control layer classmates want to do the test function control layer, you can use a simulation model Mock layer (assuming that the interface and defined, but not yet implemented function), then test, so there is no need to wait for the model layer is responsible for the students finished .

3.1 Download and install

And, like JUnit, you can download jar package and import the project, if the project is a Maven project, you can add the following dependence in the pom.xml file:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.21.0</version>
    <scope>test</scope>
</dependency>
复制代码

When in actual use, but also need to add JUnit dependency. (But not that mockito dependent on JUnit, the project is only dependent on the JUnit)

3.2 simple to use

Here only a simple example, as follows:

public class ApplicationTest {

    //有返回值方法
    public int calcSum(int a, int b) {
        return 1;
    }

    //无返回值方法
    public void noReturn() {

    }

    @Test
    //设置单个返回值
    public void testOneReturn() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(10, 10)).thenReturn(10);
        assertEquals(10,test.calcSum(10, 10));
    }

    @Test
    //设置多个返回值,按顺序校验
    public void testMultiReturn() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(10, 10)).thenReturn(10).thenReturn(20);
        assertEquals(10, test.calcSum(10, 10));
        assertEquals(20, test.calcSum(10, 10));
    }

    @Test
    //根据输入参数不同来定义不同的返回值
    public void testMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(0,0)).thenReturn(1);
        when(test.calcSum(1,1)).thenReturn(0);
        assertEquals(1, test.calcSum(0, 0));
        assertEquals(0, test.calcSum(1, 1));
    }

    @Test
    //返回值不依赖输入
    public void testNotMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(anyInt(),anyInt())).thenReturn(-1);
        assertEquals(-1, test.calcSum(10, 10));
        assertEquals(-1, test.calcSum(100, -100));
    }

    @Test
    //根据返回值的类型来决定输出
    public void testReturnTypeOfMethodParam() {
        ApplicationTest test = mock(ApplicationTest.class);
        when(test.calcSum(isA(Integer.class), isA(Integer.class))).thenReturn(-100);
        assertEquals(-100, test.calcSum(100, 100));
        assertEquals(-100, test.calcSum(111,111));
    }

    @Test
    //行为验证,主要用于验证方法是否被调用
    public void testBehavior() {
        ApplicationTest test = mock(ApplicationTest.class);
        test.calcSum(10, 10);
        test.calcSum(10, 10);
        //times(2)表示被调用两次
        verify(test, times(2)).calcSum(10, 10);
    }
}

复制代码

First of all, in every way we are constructed in a Mock object, that is

ApplicationTest test = mock(ApplicationTest.class);
复制代码

After construction is completed, you can do some configuration, take testOneReturn method is used when (...). ThenReturn (...) a way to configure the mock objects, when the argument is a method call, for example, test.calcSum (10, 10), threnReturn parameter is provided which return of a call. So when (test.calcSum (10, 10)) thenReturn (10); this line of code means "when calling test.calcSum (10,10), it should return to 10", and then call assertEquals (10, test .calcSum (10, 10)); verify correct.

Here you might be a bit strange, the code calcSum in any case should return -1 fishes ah, that this line whether it is possible to test it? The answer is energy! Because we use when (...). ThenReturn (...) In this method call is set up to do, that is, the return value is defined here as our custom, no matter calcSum is how to achieve, as long as we are in accordance with the provisions when set the value in the form of a call (example is test.calcSum (10, 10)), then it must be returned thenReturn pair ().

Other methods do not say, and testOneReturn () almost, but also made a comment, it should be easy to understand.

4 SpringBoot Test

4.1 simple demonstration

SpringBoot Test module contains JUnit, Mockito that rely, at the time of testing items Spring Boot, only need to add a dependency to the Boot Test Spring, as follows:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>根据官网发布的版本进行选择,记得避免版本冲突</version>
    <scope>test</scope>
</dependency>
复制代码

Standard Spring Boot of three MVC code, I omitted a very simple, straightforward look at the test class.

package top.yeonon.springtest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import top.yeonon.springtest.controller.UserController;
import top.yeonon.springtest.repository.UserRepository;
import top.yeonon.springtest.service.UserService;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
 * @Author yeonon
 * @date 2018/10/15 0015 18:21
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    private MockMvc mockMvc;

    @Autowired
    private UserController userController;

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Before
    public void setUp() {
        //构造mockMvc
        mockMvc = MockMvcBuilders.standaloneSetup(userController, userService, userRepository).build();
    }

    @Test
    public void testUserService() throws Exception {
        RequestBuilder request = null;

        //1. 注册用户

        request = post("/users")
                .param("username", "yeonon")
                .param("password", "admin")
                .contentType(MediaType.APPLICATION_JSON_UTF8);

        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("注册成功"));  //在业务代码中,如果成功就会返回“注册成功”;

        //2. 根据id获取用户
        request = get("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("{\"id\":1,\"username\":\"yeonon\",\"password\":\"admin\"}"));

        //3. 修改用户信息
        request = put("/users")
                .param("username", "weiyanyu")
                .param("password", "aaa")
                .param("id", "1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("更新成功"));

        //4. 再次获取信息
        request = get("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("{\"id\":1,\"username\":\"weiyanyu\",\"password\":\"aaa\"}"));

        //5. 删除用户
        request = delete("/users/1");
        mockMvc.perform(request)
                .andExpect(status().is(200))
                .andExpect(content().string("删除成功"));

    }
}

复制代码

mockMvc Spring is a class package, can be seen from the name is a simulation for the MVC, in fact, it is. The entire testing process can be divided into the following steps:

  1. Construction mockMvc objects, and passing through MockMvcBuilders corresponding Bean (if you pass incomplete, may lead to failure shall be reported to inject Bean null pointer exception).
  2. Gets a RequestBuilder object can be obtained by MockMvcRequestBuilders.get (), MockMvcRequestBuilders.post () method and the like.
  3. The RequestBuilder objects passed mockMvc.perform () method, which returns a ResultActions object that represents a certain behavior.
  4. ResultActions API provided by the object to return the result of verification done, e.g. andExpect, andDo, andReturn like. Wherein the received parameter andExpect ResultMatcher is a type of the object, there are many methods may be used for our use in MockMvcResultMatchers, such as status, content and the like.

This completes a web test. Here the way coding problem, in this test environment, the default encoding is not UTF-8 (like ISO-xxx, specific forget), so if there are Chinese controller to return without special treatment, it may be wrong . One solution is to modify the properties of the controller produces the @RequestMapping, as follows:

@DeleteMapping(value = "{id}",produces = "application/json;charset=UTF-8")
public String deleteUser(@PathVariable("id") Long id) {
    return userService.deleteUser(id);
}
复制代码

4.2 h2 memory database

The small test project, in fact, used the h2 database. h2 is a Java language development database, can be embedded directly into the application, and the application packaged and released, platform-independent, it also supports memory mode, it is well suited for test environments. Generally For convenience, when used in the test environment, .sql file will be loaded into the project h2, and then use memory model test, all operations are performed in memory in the memory mode, not for persistence , so no need to worry about dirty database production environment.

spring boot also has support for h2, we only need to add the relevant dependent h2 in the project and do a small amount of configuration can be used, as follows:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.196</version>
</dependency>
复制代码

Configuration is as follows:

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=admin
spring.datasource.password=admin

spring.jpa.database=h2
spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true
spring.h2.console.path=/console

复制代码
  1. datasource conventional four configuration is not to say, hard to understand.
  2. spring.jpa.database. Because the project uses jpa, so here jpa configure the look of the target database type is h2.
  3. spring.h2.console.enabled. Whether to enable console h2, h2 provides a web console, user-friendly CRUD data.
  4. spring.h2.console.path. Path consoles, such as the configuration of the above is / console, use the time you can enter in the browser address bar http: // localhost: port / console into the console.

After starting the program, enter the url into the browser h2 console, as follows:

iaBLm4.png

After doing the configuration, enter the user name and password, click on Connect to enter the console interface, as shown below:

iaBO0J.png

In the space can enter SQL statements conform to specifications to operate the database, you can see the left sidebar there is a T_USER database table, which is the JPA to help us to create, in h2, the default name of the table are in uppercase, but in write SQL statements when you can use lowercase, h2 will help us be converted to uppercase. As follows:

iaDC6O.png

About h2 database of the first so as h2 interface also JDBC-compliant, so if you are familiar with JDBC, then, do not be too concerned about the operational details of h2.

5 TDD

TDD i.e., Test-Driven Development (test-driven development). Name may not be so easy to understand its meaning, what is the measured test-driven development? Why use the test to start the development? How to test-driven development? We will focus on the following three questions brief TDD.

5.1 What is test-driven development

If there is no prior contact with a similar concept, most people's understanding of the test should be: first write the code, and then after the test is completed, the purpose of the test is to verify the correctness of the program, the quality of performance, availability, scalability, and so on. The test-driven development, by contrast, TDD advocate is to write a test program, then write code to satisfy the test is successful, so that the test program can, as long as the test case write good things to consider when refactoring code on it a lot less just let the code to pass the test.

5.2 Why Test Driven Development

TDD and traditional way after the first test developed compared to at least the following several advantages:

  • Reduce the burden on developers, developers only need to write the code through test cases, do not need tangled mess in a variety of requirements.
  • It has a strong adaptability to changes in demand, but when demand changes, only need to modify the test according to the needs, and then write again or modify the code to adapt the test cases, to avoid "penny wise, pound-foolish" to happen.
  • Demand a clear, written test advance may urge us to sort out the demand, instead of writing code that is written only to find half the demand is not clear, leading to "rework."
  • High efficiency, so-called Preparation may quicken the work, although written tests in advance takes a long time and a lot of energy, but they consume are worth it. If you do not write tests in advance, and ultimately needs its own manual testing, manual testing while they take time to start the application, the interface between the various jumps back and forth, in fact, the time it takes to write automated test much more than before.

5.3 How to Test Driven Development

In fact, faint above mentioned this, but did not give a clear idea or step, the following is the basic flow of TDD:

  1. Write a test case
  2. Run the test program, this time should not pass the test
  3. Write code, the goal is to make the code through test cases.
  4. Re-run the test program at this time or if the test is not passed, go back to step 3, and so forth, until the test passes.

There is a problem, step 2 is clearly yes to fail (because no write specific code), why run a test program it? Because the reasons for failure are many, not necessarily because there is no write specific code causes, there may be a test environment problems caused, so run it once to see the error report, if a test environment problems, then first try to repair test environment, or if developed in a test environment problems, it may cause no matter how impossible to write programs that passed the tests (since each test will issue a test environment because the test will fail).

In fact, TDD is far more than that, there are many, many benefits, there are some drawbacks, because I am understanding of TDD is not too much, because usually lazy, did not develop the habit of writing tests first, so do not say the recommended search for relevant information on their own learning, when here is "start a discussion" of it.

6 Summary

This paper introduces the concept of unit testing, incidentally, introduced two testing framework JUnit, Mockito simple to use, and then also combined with Spring Boot project to do a little practice, we hope to help the reader. Finally, a brief introduction to TDD (Test Driven Development), TDD is a core technology in agile development, can effectively improve the development efficiency and product quality, TDD actually be a science, if you want in-depth study, to recommend here look.

Guess you like

Origin juejin.im/post/5d6e808d5188252e96191e51