[Software testing] Junit unit testing

foreword

There is a software testing class this semester, and the coursework is to write experimental reports on some testing tools. This is indeed a good opportunity. After all, as a full-stack engineer, how can you not even know about testing (laughs and tears).

This article briefly describes unit testing and Java's single testing tool junit, as well as the simple use of the mokito framework. In addition, I will also include other testing related articles in this series.

1. Unit testing

1. What is unit testing?

To borrow the words of Baidu Encyclopedia,

Unit testing refers to the inspection and verification of the smallest testable unit of software. For the meaning of a unit in unit testing, generally speaking, it is necessary to determine its specific meaning according to the actual situation. For example, a unit in C language refers to a function, in Java, a unit refers to a class, and graphical software can refer to a window or a menu. Wait. In general, a unit is the smallest human-defined functional module under test. Unit testing is the lowest level of testing activity to be performed during software development, where individual units of software are tested in isolation from the rest of the program.

Here are a few key points:

  • Units are man-made
  • Unit tests are independent units, separated from other parts.

2. Why do you need unit testing?

Let me talk about my own feelings here. As far as I am concerned, I have no habit of unit testing. I feel that unit testing will be very time-consuming. At the same time, I think it is not worth the time spent. All are simple crud projects.
But as the projects I develop become larger and the requirements become more complex, I gradually find that the quality of the projects I do is becoming more and more unstable, and there are often some strange bugs. When a bug occurs, we often have to locate the problem. For example, I made a general IoT platform some time ago. When recording a demonstration video, I entered a correction formula (supporting four arithmetic operations), and it worked normally under other devices at that time, but there was an abnormality that time. Fortunately, the whole project was made by myself, and I knew some implementation details. After debugging for a while, I found that it was an algorithm problem. At that time, I suddenly remembered that when I wrote it, I realized that the algorithm implementation does not support negative numbers. The negative number operation becomes the form of "(0-x)", and the data uploaded by that device happens to be a negative number, so there is a problem.

Fortunately, it is full-stack development, and everything is done by myself. If this project is developed by a team, I estimate that the time it takes to locate bugs will increase exponentially.

Because in large-scale testing such as integration testing, it takes too long to locate bugs, so we need unit testing to ensure the correctness of each small module. Although it will take more time, it is worth the time compared to the time spent on bugs and bug fixes.

In the process of developing a project, many times it is to solve the legacy of previous bugs.
insert image description here

I have seen relevant summaries on the Internet, and they are very well written to share - what exactly is unit testing? What should be done?

  • Unit testing is very important to our product quality.
  • Unit testing is the lowest type of testing in all tests. It is the first and most important part. It is the only test that is guaranteed to achieve 100% code coverage. It is the foundation and premise of the entire software testing process. , Unit testing prevents the late development from getting out of control due to too many bugs, and the cost performance of unit testing is the best.
  • According to statistics, about 80% of the errors are introduced in the software design stage, and the cost of fixing a software error will rise with the progress of the software life cycle. The later a bug is discovered, the more expensive it is to fix it, and it increases exponentially.
  • As a coder and the main performer of unit testing, he is the only one who can produce defect-free programs. No one else can do this. Code specification, optimization, and testability.
  • Refactor with confidence
  • Automate execution three-thousand times

2. Junit

1. What is junit

JUnit is a unit testing framework for the Java language. Founded by Kent Beck and Erich Gamma, it has grown to be the most successful of the xUnit family of sUnits derived from Kent Beck. JUnit has its own ecosystem of JUnit extensions.
At present, junit has developed to junit5, which has changed a lot compared to junit4. JUnit5 consists of several different modules from three different sub-projects.
JUnit 5=JUnit Platform+JUnit Jupiter+JUnit Vintage

See the official website for details

Note: junit is basically the mainstream of Java unit testing, and most Java projects today have junit

2.Junit concept - assertion

Students who have just been exposed to unit testing will definitely wonder what the assert method means and what is an assertion when learning junit. When I first came into contact, it was like this, wondering what the assertion was for.

In fact, assertions are actually some helper functions, which are used to help us determine whether the method under test works as expected. Usually, these helper functions are called assertions.

3. Simple use of Junit

The following demo is a maven project

①Import dependencies

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.1</version>
        <scope>test</scope>
    </dependency>
    <!-- ... -->
</dependencies>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>

②Write test cases

Here is an example from the official website

import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {
    
    

    private final Calculator calculator = new Calculator();

    @Test
    void addition() {
    
    
        assertEquals(2, calculator.add(1, 1));
    }

}

The above example asserts that the return value of calculator.add(1, 1) will be equal to 2.

It will be very convenient to run the test in the idea, just click the run icon.
insert image description here
If it is not in the idea, just add a mian function to run it.

If the running assertion is correct, the program will be as follows:
insert image description here
If the assertion is wrong, junit will throw you an AssertionFailedError exception and tell you what went wrong
insert image description here

4. Use of junit in SpringBoot environment

Of course, in actual development, such as in the SpringBoot environment, many business code classes are injected into the Spring container, and there are dependencies between other injected classes. It is obviously unrealistic to create a test object as before. . So what is the solution to this problem?
Let me introduce the use of junit in the SpringBoot+SSM project.

①Import dependencies

It integrates dependencies in SpringBoot. If we need to test related dependencies, we only need to introduce the corresponding test modules.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

②Write test cases

test object class

package com.example.demo.service;

import org.springframework.stereotype.Component;

@Component
public class Junit5Test {
    
    
    public int add(int i,int j){
    
    
        System.out.println("-----------add被执行了---------------");
        return i+j;
    }
    public int doAdd(int i,int j){
    
    
        System.out.println("------------doAdd被执行了--------------");
        //被mock的函数会先执行,且只会执行一次
        System.out.println(add(i,j));
        return add(i,j);
    }
}

test case

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
    
    
    @Autowired
    Junit5Test junit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
    
    
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
    
    
        System.out.println("each");
    }

    @Test
    void getDeviceStatistic() {
    
    
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}

If you need the SpringBoot context, you only need to add a @SpringBootTest annotation to it. Of course, in old projects we may see @RunWith(SpringRunner.class). The former is the writing method of junit5, and the latter is the writing method of junit4.

When we need the test object in the spring container, we just inject it normally.

@Autowired
Junit5Test junit5Test;

3. Mock data - the use of mockito framework

1.mock

In the actual development and single test, our test object may need to request network data or change the database, but we don't want it to change. At this time, we can use the mockito framework to mock the data.

The so-called mock means that if the code we write depends on some objects, and these objects are difficult to create manually (ie, do not know how to initialize, etc., such as HttpRequest and other objects), then use a virtual object to test. Because it passes in a class file, the static code block will still be executed, but the constructor and instance code blocks will not be executed .

2. Piling Stub

The so-called stubbing Stub is used to provide the test data required for testing. Because it is a mock object, some methods may not know the return value, so we need to assume the return value. Corresponding responses can be set for various interactions, that is, to set the return value of a method call, using when(…).thenReturn(…) and doReturn(…).when(…).

for example:


//You can mock concrete classes, not only interfaces
 LinkedList mockedList = mock(LinkedList.class);
 
 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());
  • doReturn().when() has no side effects. The method is not executed while piling.
  • when().thenReturn() has side effects, which means that the method will be executed first while piling, which may cause certain side effects.

3. @MockBean and @SpyBean

Of course, in the SpringBoot environment, you can also directly use @SpyBean and @MockBean annotations to replace the injected object of @Autowired, so that there is a virtual object.

@MockBean
If only @MockBean is used, the modified object will be mocked, so that the add() method of Junit5Test will no longer execute the specific details, but MockBean will mock all the methods of the target object, so the test cannot be actually executed. , it cannot be tested.

@SpyBean
and in some cases we need to execute real methods, we only want to mock some methods, then we can use @SpyBean.
The spyJunit5Test decorated with @SpyBean is a real object, only when(spyJunit5Test.add(1,1)).thenReturn(2);, the add method is stubbed, and other methods are still called.

Here is an example

package com.example.demo.service;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;

//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
    
    
//    @Autowired
//    Junit5Test junit5Test;
    //介于@Autowired和@MockBean之间的注解,当配置了when时使用mock,没有进行打桩则走正常方法
    @SpyBean
    Junit5Test spyJunit5Test;
    //完全使用mock方法,方法都不会去真正执行。当调用mockBean修饰的方法时,不会去真正执行该方法,只会返回打桩后的值,如果没有打桩时会返回默认值,比如int就返回0。
//    @MockBean
//    Junit5Test mockJunit5Test;
    //会初始化一次
    @BeforeAll
    static void init(){
    
    
        System.out.println("init");
    }
    //所有测试方法前都会执行一遍
    @BeforeEach
    void each(){
    
    
        System.out.println("each");
    }

    @Test
    void getDeviceStatistic() {
    
    
        Assertions.assertEquals(1,1);
    }
    @Test
    void testDeviceStatisticByMock() {
    
    

        //配置mock
        when(spyJunit5Test.add(1,1)).thenReturn(2);
        Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
    }
}

Finally, I wish everyone a Happy Programmer's Day!

Guess you like

Origin blog.csdn.net/qq_46101869/article/details/120942569