Springboot单元测试

Springboot单元测试

SpringBoot 单元测试

软件测试中主要分为单元测试,集成测试,系统测试

  1. 单元测试

单元测试是针对软件系统中 最小的可测试单元 ——单元进行的测试。这些单元可能是一个方法、一个类或一个模块等。单元测试通常由开发人员编写,旨在检测单元是否能够按照预期工作,并且通常在没有依赖项的情况下运行。单元测试的主要目的是确保软件的每个单独的部分都能够正确地运行。通常由 开发人员 编写和执行。

  1. 集成测试

集成测试是将 不同的单元组合在一起 进行测试,以确定它们能否协同工作。它旨在检测单元之间的接口和协作是否正确,以及整个系统是否按预期工作。集成测试通常由 开发人员和测试人员 一起编写和执行,它们可以在各种不同的环境中进行测试,包括模拟和实际硬件设备。

  1. 系统测试

系统测试是对 整个软件系统进 行测试,以确保其满足用户需求和规范。这是一个黑盒测试,即测试人员只关心系统的输入和输出,而不关心内部实现。系统测试通常由 测试团队 执行,可以涵盖各种测试类型,如功能测试、性能测试、安全性测试、易用性测试等。

单元测试

  • 相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • 测试示例
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
@Slf4j
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class Test {

    @Test
    @DisplayName("布尔断言")
    void test1() {
        assertTrue(1 == 1);
        assertFalse(2 == 1);
    }

    @Test
    @DisplayName("Null断言")
    void test2() {
        assertNull(null);
        assertNotNull(new Object());
    }

    @Test
    @DisplayName("引用断言")
    void test3() {
        String s1 = "123abc";
        String s2 = "123abc";
        String s3 = new String("123abc");
        assertSame(s1, s2);
        assertNotSame(s1, s3);
    }

    @Test
    @DisplayName("异常断言")
    void test4() {
        assertThrows(ArithmeticException.class, () -> {
            int i = 5 / 0;
        });
    }

    @Test
    @DisplayName("超时断言")
    void test5() {
        assertTimeout(Duration.ofSeconds(3),() ->{
            Thread.sleep(2000);
        });
    }

    @Test
    @DisplayName("数组断言")
    void test6() {
        List<Integer> integers = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            integers.add(i);
        }
        Object[] objects = integers.toArray();
        Integer[] integers1 = {0,1,2};
        assertArrayEquals(objects,integers1);
    }

    @Test
    @DisplayName("组合断言")
    void test7() {
        assertAll("组合测试",
                () -> {
                    String s1 = "123abc";
                    String s2 = "123abc";
                    String s3 = new String("123abc");
                    assertSame(s1, s2);
                    assertNotSame(s1, s3);
                },
                () -> {
                    assertThrows(ArithmeticException.class, () -> {
                        int i = 5 / 0;
                    });
                },
                () -> {
                    assertNull(null);
                }
        );
    }

    @RepeatedTest(3)
    @DisplayName("重复测试")
    void test8() {
        System.out.println("调用");
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3})
    @DisplayName("参数化测试")
    void test9(int a) {
        assertTrue(a > 0 && a < 4);
    }

    @Nested
    @DisplayName("内嵌测试")
    class OrderTestClas {
        @Test
        @DisplayName("内嵌test1")
        void test1() {
            System.out.println("test1");
        }

        @Test
        @Disabled
        @DisplayName("内嵌test2 Disabled")
        void test2() {
            System.out.println("test2Disabled");
        }
    }

    @Test
    @BeforeAll
    public static void  test10() {
        log.info("============BeforeAll============");
    }
    @Test
    @BeforeEach
    public  void  test11() {
        log.info("============BeforeEach============");
    }

    @Test
    @AfterAll
    public static void  test12() {
        log.info("============AfterAll============");
    }
    @Test
    @AfterEach
    public  void  test13() {
        log.info("============AfterEach============");
    }
}

尽管 @SpringBootTest 注解隐式地包含了 @ExtendWith(SpringExtension.class) 注解,但是建议仍然在测试类上使用 @ExtendWith(SpringExtension.class) 注解来明确地声明使用 SpringExtension 扩展。这样做的好处是可以在测试类中更加明确地表达所使用的测试框架和扩展,同时也能保证更好的可读性和可维护性。

1. 相关注解

  1. @SpringBootTest:用于在测试环境中启动完整的Spring应用程序上下文,用于测试整个应用程序的集成。

  2. @WebMvcTest:用于测试Web层的控制器,可以通过模拟HTTP请求来测试控制器的行为。

  3. @DataJpaTest:用于测试数据访问层的代码,可以使用嵌入式数据库测试数据访问代码。

  4. @RestClientTest:用于测试使用RestTemplate进行远程调用的代码。

  5. @MockBean:用于创建模拟对象,以便在测试中替换真实对象。通过将依赖项替换为模拟对象,可以隔离和测试代码的单个部分。

  6. @Mock:用于创建模拟对象,与@MockBean类似,但是@MockBean适用于Spring管理的Bean,而@Mock适用于不受Spring管理的Bean。

  7. @Autowired:用于自动装配依赖项,可以将Spring管理的Bean注入到测试中。

  8. @Test:JUnit5测试方法的标准注解,表示方法是测试方法。

  9. @BeforeEach:JUnit5测试方法的标准注解,表示在每个单元测试之前执行。

  10. @AfterEach:JUnit5测试方法的标准注解,表示在每个单元测试之后执行。

  11. @BeforeAll:JUnit5测试方法的标准注解,表示在所有单元测试之前执行。

  12. @AfterAll:JUnit5测试方法的标准注解,表示在所有单元测试之后执行。

  13. @DisplayName:JUnit测试方法的标准注解,表示测试方法的显示名称。

  14. @Timeout:表示测试方法运行如果超过了指定时间将会返回错误

  15. @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

  16. @Tag:表示单元测试类别,类似于JUnit4中的@Categories

  17. @ExtendWith:为测试类或测试方法提供扩展类引用 类似于JUnit4中的@RunWith

  18. @ParameterizedTest:表示方法是参数化测试

  19. @RepeatedTest:表示方法可重复执行

2. 断言机制

断言机制: 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。

  1. 简单断言
  • assertEquals 判断两个对象或者两个原始类型是否相等

  • assertNotEquals 判断两个对象或者两个原始类型不等

  • assertSame 判断两个对象引用是否指向同一对象

  • assertNotSame 判断两个对象引用是否指向不同对象。

  • assertTrue 判断给定的布尔值是否为true

  • assertFalse 判断给定的布尔值是否为False

  • assertNull 判断给定的对象是否为Null

  • assertNotNull 判断给定的对象是否不为null

  1. 数组断言
  • assertArrayEquals 判断两个对象或原始类型数组是否相等
  1. 组合断言
  • assertAll 当它内部所有断言正确执行完才算通过
  1. 超时断言
  • assertTimeout() 为测试方法设置了超时时间,如果测试方法超过指定的时间将会抛出异常

3.参数化测试

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

  • @ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource:表示为参数化测试提供一个null的入参
  • @EnumSource:表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。 让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。 如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。 只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

  • JUnit5的注解与JUnit4的注解有所变化,可以参考JUnit5文档