单元测试

单元测试

  • Command + shift + T :创建类对应的测试类

Junit单元测试模板

Test

import com.alibaba.fastjson.JSON;
import com.bruce.boot.springbootdemo.SpringbootDemoApplication;
import com.bruce.boot.springbootdemo.bean.User;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.context.WebApplicationContext;

import java.nio.charset.StandardCharsets;

@Slf4j
@SpringBootTest(classes = SpringbootDemoApplication.class)
class HelloControllerTest {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    private HttpHeaders httpHeaders;

    private static final String BASIC_URL_PREFIX = "/boot/";

    /**
     * @BeforeEach 表示在每个测试类开始前执行初始化操作
     */
    @BeforeEach
    public void init() {
        //mock初始化
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
        //header初始化,构造请求头
        LinkedMultiValueMap<String, String> headerMap = new LinkedMultiValueMap<>();
        headerMap.add("Content-Type", "application/json");
        headerMap.add("Accept", "application/json");
        headerMap.add("Cookie", "sessionId=abc123");
        headerMap.add("token", "sdafasdfff");
        httpHeaders = new HttpHeaders();
        httpHeaders.addAll(headerMap);
    }

    /**
     * get请求示例
     *
     * @SneakyThrows:表示忽略异常
     */
    @SneakyThrows
    @Test
    void hello() {
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(BASIC_URL_PREFIX + "hello")
                .headers(httpHeaders).contentType(MediaType.APPLICATION_JSON)
        ).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        String content = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
        log.info("test content:{}", content);
    }

    @Test
    void testHello() {
    }

    /**
     * 测试header取值
     */
    @SneakyThrows
    @Test
    void saveUser() {
        User user = new User();
        user.setName("bruce");
        user.setId(23);
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(BASIC_URL_PREFIX + "saveUser")
                .headers(httpHeaders)
                        .content(JSON.toJSONString(user))
                .contentType(MediaType.APPLICATION_JSON)
        ).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        String content = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
        log.info("test content:{}", content);
    }

    /**
     *
     */
    @SneakyThrows
    @Test
    void getHeader() {
        User user = new User();
        user.setName("bruce");
        user.setId(23);
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(BASIC_URL_PREFIX + "getHeader")
                .headers(httpHeaders)
                .content(JSON.toJSONString(user))
                .contentType(MediaType.APPLICATION_JSON)
        ).andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
        String content = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
        log.info("test content:{}", content);
    }
}

Controller

@Slf4j
@RequestMapping("/boot")
@RestController
public class HelloController {

    @Resource
    private UserService userService;

    @GetMapping("/hello")
    public String hello(String name) {
        for (int i = 0; i < 30; i++) {
            log.info("info log name:{}", name);
            log.error("error log name:{}", name);
        }
        log.info("info log name:{}", name);
        log.error("error log name:{}", name);

        return "hello " + name;
    }

    @GetMapping("/phone")
    public Object hello() {
        Phone iphone = Phone.builder().brand("iphone").size(20).type("15").build();
        if (true) {
            System.out.println("dd");
            System.out.println("dd");
            System.out.println("dd");
            System.out.println("dd");

        }
        return iphone;
    }

    @GetMapping("/getUser")
    public Object getUser() {
        User user = userService.getUserById(1);
        return user;
    }

    @PostMapping("/saveUser")
    public Object saveUser(@RequestBody User user) {
        return user;
    }

    @PostMapping("/getHeader")
    public Object saveUser(@RequestHeader(name = "token") String token) {
        return token;
    }
}

细节图

image.png

image.png
image.png
image.png
image.png
image.png
image.png
image.png

Mockito单元测试

  • 注意,使用@SpringbootTest时,要Mock的类要使用@MockBean注解进行代理,否则加载的会是容器中真实的实例。

单元测试类

package com.bruce.boot.springbootdemo.test;

import com.alibaba.fastjson.JSON;
import com.bruce.boot.springbootdemo.bean.User;
import com.bruce.boot.springbootdemo.controller.HelloController;
import com.bruce.boot.springbootdemo.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/***
 * @ClassName: MockitoTest
 * @Description:
 * @author: lairongfeng
 * @Date: 2024/8/27  01:12
 * @version : 1.0
 */
@Slf4j
@SpringBootTest
@ExtendWith(SpringExtension.class)
public class MockitoTest {

    /**
     * 通过 @MockBean注解提前定义mock代理类
     */
    @Resource
    @MockBean
    private UserService userService;

    @Resource
    private HelloController helloController;


    /**
     * 手动定义代理类
     */
    @Test
    public void updateUserTestManual() {
        //定义mock代理类
        userService = Mockito.mock(UserService.class);
        userService.updateUer(Mockito.any());
    }

    @Test
    public void updateUserTest() {
        //使用@MockBean注解提前定义mock代理类,测试类直接构造想要的出参
        //定义想要的理想返回值
        Mockito.when(userService.updateUer(Mockito.any())).thenReturn(new User("aa", 1));
        Mockito.when(userService.rpcRequest(Mockito.any())).thenReturn("OK");
        Object user = helloController.updateUser(new User("bb", 2));
        log.info(JSON.toJSONString(user));
    }

    @Test
    public void rpcTest() {
        //使用@MockBean注解提前定义mock代理类,测试类直接构造想要的出参
        //定义想要的理想返回值
        Mockito.when(userService.rpcRequest(Mockito.any())).thenReturn("OK");
        String str = userService.rpcRequest(Mockito.any());
        log.info(str);
    }

}

@PostMapping("/updateUser")
public Object updateUser(@RequestBody User user) {
  /**
         * 模拟修改用户的数据库、rpc请求、发送邮件通知都未实现场景
         */
  user = userService.updateUer(user);
  System.out.println(userService.rpcRequest(user));
  userService.sendEmail();
  return user;
}

依赖

<!-- 添加 Mockito 依赖 -->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.11.2</version>
  <scope>test</scope>
</dependency>

springboot单元测试-JUnit5

4.1. 整合

SpringBoot 提供一系列测试工具集及注解方便我们进行测试。

spring-boot-test提供核心测试能力,spring-boot-test-autoconfigure 提供测试的一些自动配置。

我们只需要导入spring-boot-starter-test 即可整合测试

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

spring-boot-starter-test 默认提供了以下库供我们测试使用

4.2. 测试

测试时可以参考官网写法

4.2.0 组件测试

直接@Autowired容器中的组件进行测试

4.2.1 注解

JUnit5的注解与JUnit4的注解有所变化

https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
  • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
  • @DisplayName :为测试类或者测试方法设置展示名称
  • @BeforeEach :表示在每个单元测试之前执行
  • @AfterEach :表示在每个单元测试之后执行
  • @BeforeAll :表示在所有单元测试之前执行
  • @AfterAll :表示在所有单元测试之后执行
  • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith :为测试类或测试方法提供扩展类引用
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @DisplayName("😱")
    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

4.2.2 断言

断言官网写法参考

方法 说明
assertEquals 判断两个对象或两个原始类型是否相等
assertNotEquals 判断两个对象或两个原始类型是否不相等
assertSame 判断两个对象引用是否指向同一个对象
assertNotSame 判断两个对象引用是否指向不同的对象
assertTrue 判断给定的布尔值是否为 true
assertFalse 判断给定的布尔值是否为 false
assertNull 判断给定的对象引用是否为 null
assertNotNull 判断给定的对象引用是否不为 null
assertArrayEquals 数组断言
assertAll 组合断言
assertThrows 异常断言
assertTimeout 超时断言
fail 快速失败

4.2.3 嵌套测试

JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

@DisplayName("A stack")
class TestingAStackDemo {
 
    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

4.2.4 参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource: 表示为参数化测试提供一个null的入参

@EnumSource: 表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method")    //指定方法名
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

细节图

image.png
image.png
image.png
image.png

参考

posted @ 2025-06-09 01:34  卡斯特梅的雨伞  阅读(11)  评论(0)    收藏  举报