Spring Boot单元和集成测试概述 | rieckpil

解道jdon 2021-05-04 18:52:58
spring 测试 boot 集成 单元


单元和集成测试是您作为开发人员日常生活不可或缺的一部分。特别是对于Spring Boot而言,新手为他们的应用程序编写有意义的测试是一个障碍:

  • 从哪里开始我的测试工作?
  • Spring Boot如何帮助我编写高效的测试?
  • 我应该使用哪些库?

通过此博客,您将获得有关单元引导和集成测试如何与Spring Boot一起工作的概述。最重要的是,您将学习Spring首先要关注的功能和库。本文充当聚合器,在许多地方,您都可以找到其他文章和指南的链接,这些文章和指南对这些概念进行了更详细的说明。

 

使用Spring Boot进行单元测试

单元测试为您的测试策略奠定了基础。您使用Spring Initializr引导的每个Spring Boot项目都具有编写单元测试的坚实基础。几乎没有什么可设置的,因为Spring Boot Starter Test包含所有必要的构建基块。

除了包含和管理Spring Test的版本外,此Spring Boot Starter包括并管理以下库的版本:

  • JUnit 4/5
  • Mockito
  • 断言库,如AssertJ,Hamcrest,JsonPath等。

大多数时候,您的单元测试不需要任何特定的Spring Boot或Spring Test功能,因为它们仅依赖JUnit和Mockito。

使用单元测试,您可以单独测试*Service类,例如模拟Mock要测试的类的每个协作者:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
 
import java.math.BigDecimal;
 
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
 
@ExtendWith(MockitoExtension.class) // register the Mockito extension
public class PricingServiceTest {
 
  @Mock // // Instruct Mockito to mock this object
  private ProductVerifier mockedProductVerifier;
 
  @Test
  public void shouldReturnCheapPriceWhenProductIsInStockOfCompetitor() {
    when(mockedProductVerifier.isCurrentlyInStockOfCompetitor("AirPods"))
      .thenReturn(true); //Specify what boolean value to return
 
    PricingService cut = new PricingService(mockedProductVerifier);
 
    assertEquals(new BigDecimal("99.99"), cut.calculatePrice("AirPods"));
  }
}

从上面import的测试类的部分可以看到,Spring根本没有import进来。因此,您可以应用对任何其他Java应用程序进行单元测试的技术和知识。

因此,重要的是要学习JUnit 4/5和Mockito的基础知识,以充分利用您的单元测试。

对于应用程序的某些部分,单元测试不会带来很多好处。持久层或测试HTTP客户端就是一个很好的例子。测试应用程序的这些部分,最终将几乎复制您的实现,因为您必须模拟与其他类的大量交互。

这里更好的方法是使用切片的Spring Context,您可以使用Spring Boot测试注释轻松地自动配置它。

 

使用切片的Spring上下文进行测试

在传统的单元测试之上,您可以使用Spring Boot编写针对应用程序特定部分(切片)的测试。SpringTestContext框架和Spring Boot一起将为Spring Context量身定制具有足够用于特定测试的组件的Spring Context。

这些测试的目的是在不启动整个应用程序的情况下单独测试应用程序的特定部分。这样既可以缩短测试执行时间,又可以减少对大量测试设置的需求。

如何命名此类测试?我认为,它们在单元测试或集成测试类别中均不会下降100%。一些开发人员将它们称为单元测试,因为它们例如独立测试一个控制器。其他开发人员将它们归类为集成测试,因为涉及到Spring支持。无论您如何命名,至少在团队中都要确保有一致的理解。

Spring Boot提供了大量的注解来测试您的应用程序的不同部分隔离:@JsonTest,@WebMvcTest,@DataMongoTest,@JdbcTest,等。

它们全部自动配置切片的Spring,TestContext并且仅包含与测试应用程序的特定部分相关的Spring Bean。我在整篇文章中都专门介绍了这些注释中最常见的注释,并解释了它们的用法。

两个最重要的注释(考虑首先学习它们)是:

还有一些注释可用于您应用程序的更多细分部分:

您始终可以通过以下方式显式地导入组件@Import或定义其他Spring Bean来丰富测试的自动配置上下文@TestConfiguration:

@WebMvcTest(PublicController.class)
class PublicControllerTest {
 
  @Autowired
  private MockMvc mockMvc;
 
  @Autowired
  private MeterRegistry meterRegistry;
 
  @MockBean
  private UserService userService;
 
  @TestConfiguration
  static class TestConfig {
 
    @Bean
    public MeterRegistry meterRegistry() {
      return new SimpleMeterRegistry();
    }
 
  } 
}

 

JUnit 4与JUnit 5陷阱

在回答Stack Overflow上的问题时,我经常遇到的一个大陷阱是同一测试中JUnit 4和JUnit 5(更具体地讲,JUnit Jupiter)的混合。在同一测试类中使用不同JUnit版本的API会导致意外的输出和失败。

重要的是要注意导入import,尤其是@Test注释:

// JUnit 4
import org.junit.Test;
 
// JUnit Jupiter (part of JUnit 5)
import org.junit.jupiter.api.Test;

对于JUnit 4的其他指标有:@RunWith,@Rule,@ClassRule,@Before,@BeforeClass,@After,@AfterClass。

为了避免意外混用不同的JUnit版本,从项目中排除它们有助于始终选择正确的导入:

<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>

除了Spring Boot Starter Test之外,其他测试依赖项还可能包括旧版本的JUnit:

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>${testcontainers.version}</version>
  <exclusions>
    <exclusion>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </exclusion>
  </exclusions>
</dependency>

为了避免将来包含任何(偶然的)JUnit 4依赖关系,可以使用Maven Enforcer插件并将其定义为禁止的依赖关系。一旦有人包含一个新的测试依赖关系,该依赖关系会暂时拉动JUnit 4,这将使构建失败。

请注意,从Spring Boot 2.4.0开始,Spring Boot Starter Test依赖项vintage-engine默认不再包含。

 

使用Spring Boot进行集成测试

使用集成测试,通常可以组合测试应用程序的多个组件。在大多数情况下,您将为此使用@SpringBootTest注释,并使用或从外部访问您的应用程序。

@SpringBootTest将为您的测试填充整个应用程序上下文。使用时,了解它的webEnvironment属性很重要。如果不指定此属性,则此类测试将不会启动嵌入式Servlet容器(例如Tomcat),而是使用模拟的Servlet环境。因此,您的应用程序将无法在本地端口访问。

您可以通过指定DEFINE_PORT或来覆盖此行为RANDOM_PORT:

// or DEFINED_PORT
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)

对于启动嵌入式Servlet容器的集成测试,您可以注入应用程序的端口并使用TestRestTemplateWebTestClient或从外部访问它:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class ApplicationTests {
 
  @LocalServerPort
  private Integer port;
 
  @Autowired
  private TestRestTemplate testRestTemplate;
 
  @Test
  void accessApplication() {
    System.out.println(port);
  }
}

由于SpringTestContext框架将填充整个应用程序上下文,因此您必须确保存在所有依赖的基础结构组件(例如,数据库,消息传递队列等)。

这就是Testcontainer发挥作用的地方。测试容器将为您的测试管理任何Docker容器的生命周期:

@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
 
  @Container
  public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
    .withPassword("inmemory")
    .withUsername("inmemory");
 
  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
  }
 
  @Test
  public void contextLoads() {
  }
 
}

一旦您的应用程序与其他系统通信,您就需要一个解决方案来模拟HTTP通信。这是很常见的情况,例如在应用程序启动时从远程REST API或OAuth2访问令牌中获取数据。借助WireMock,您可以存根并准备HTTP响应以模拟远程系统的存在。

此外,SpringTestContext框架具有一项巧妙的功能,可以缓存和重用以及已经启动的上下文。这可以帮助减少构建时间并大大改善您的反馈周期。

 

使用Spring Boot进行端到端测试

端到端(E2E)测试的目的是从用户的角度验证系统。这包括针对主要用户旅程的测试(例如,下订单或创建新客户)。与集成测试相比,此类测试通常涉及用户界面(如果有的话)。

您还可以在继续进行生产部署之前,针对dev或staging环境上的应用程序的已部署版本执行E2E测试。

对于使用服务器端渲染(例如Thymeleaf)或自包含系统方法(由Spring Boot后端提供前端)的应用程序,您可以使用@SpringBootTest这些测试。

一旦需要与浏览器进行交互,Selenium通常是默认选择。如果您已经与Selenium合作了一段时间,您可能会发现自己一遍又一遍地实现相同的帮助器功能。为了获得更好的开发人员体验并减少编写涉及浏览器交互的测试时的头痛,请考虑使用Selenide。Selenide是Selenium低级API之上的抽象,用于编写稳定而简洁的浏览器测试。

以下测试展示了如何使用Selenide访问和测试Spring Boot应用程序的公共页面:

@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class BookStoreTestcontainersWT {
 
  @LocalServerPort
  private Integer port;
 
  @Test
  public void shouldDisplayBook() {
 
    Configuration.timeout = 2000;
    Configuration.baseUrl = "http://localhost:" + port;
 
    open("/book-store");
 
    $(By.id("all-books")).shouldNot(Condition.exist);
    $(By.id("fetch-books")).click();
    $(By.id("all-books")).shouldBe(Condition.visible);
  }
}

对于您需要启动E2E测试的基础架构组件,Testcontainers再次扮演着重要的角色。如果您必须启动多个Docker容器,则TestcontainersDocker Compose模块会派上用场:

public static DockerComposeContainer<?> environment =
  new DockerComposeContainer<>(new File("docker-compose.yml"))
    .withExposedService("database_1", 5432, Wait.forListeningPort())
    .withExposedService("keycloak_1", 8080, Wait.forHttp("/auth").forStatusCode(200)
      .withStartupTimeout(Duration.ofSeconds(30)))
    .withExposedService("sqs_1", 9324, Wait.forListeningPort());

概括

Spring Boot为单元测试和集成测试提供了出色的支持。由于每个Spring Boot项目都包括Spring Boot Starter Test,因此它使测试成为一等公民。该入门程序为您提供了具有基本测试库的基本测试工具箱。

最重要的是,Spring Boot测试注释使对应用程序不同部分的编写测试变得轻而易举。您将获得TestContext仅包含相关Spring Bean的量身定制的Spring。

要熟悉Spring Boot项目的单元和集成测试,请考虑以下步骤:

版权声明
本文为[解道jdon]所创,转载请带上原文链接,感谢
https://www.jdon.com/55890

  1. Prototype与JQuery对比
  2. Three ways of data transmission between threads in Java and source code display
  3. Jdon causes 99% of CPU and Tomcat dies -- banq replies
  4. docker 原理之 user namespace(下)
  5. Simulating AOP injection with domain events
  6. Spring 3.1 finally adds cache support
  7. Comparison between prototype and jquery
  8. User namespace of docker principle (2)
  9. The way to learn java IO stream and XML
  10. Why does a seemingly correct code cause the Dubbo thread pool to be full
  11. 0 基础 Java 自学之路(2021年最新版)
  12. 0 basic Java self study road (latest version in 2021)
  13. c#—基础拾遗(1) 面向对象
  14. C - basic information (1) object oriented
  15. 技术分享|SQL和 NoSQL数据库之间的差异:MySQL(VS)MongoDB
  16. Technology sharing differences between SQL and NoSQL databases: MySQL (VS) mongodb
  17. PHP教程/面向对象-3~构造函数和析构函数
  18. Spring Cloud的Feign客户端入门
  19. 优化Spring Boot应用的Docker打包速度
  20. PHP tutorial / object oriented - 3 ~ constructor and destructor
  21. Introduction to feign client of spring cloud
  22. Optimizing docker packaging speed of spring boot application
  23. 尚硅谷宋红康Java基础教程2019版
  24. 尚硅谷宋红康Java基础教程2019版
  25. Song Hongkang Java foundation course 2019
  26. Song Hongkang Java foundation course 2019
  27. Redis 6 的多线程
  28. Multithreading of redis 6
  29. SpringCloud-微服务架构编码构建
  30. SpringCloud-微服务架构编码构建
  31. Linux作业控制
  32. Coding construction of springcloud microservice architecture
  33. Java中几个常用并发队列比较 | Baeldung
  34. 为什么Java后端在创业企业中并不流行? -reddit
  35. Coding construction of springcloud microservice architecture
  36. Linux job control
  37. Comparison of several common concurrent queues in Java
  38. Why is java backend not popular in start-ups- reddit
  39. docker 资源限制之 cgroup
  40. 大数据环境: hadoop和jdk部署
  41. CGroup of docker resource limitation
  42. Big data environment: Hadoop and JDK deployment
  43. Spring与Hibernate与JPA的整合(详细配置和源码)
  44. Integration of spring, hibernate and JPA (detailed configuration and source code)
  45. 《精通JPA与Hibernate:Java对象持久化技术详解》的源代码下载
  46. 《精通JPA与Hibernate:Java对象持久化技术详解》的源代码下载
  47. "Proficient in JPA and Hibernate: Java Object Persistence technology" source code download
  48. "Proficient in JPA and Hibernate: Java Object Persistence technology" source code download
  49. Redis、Kafka或RabbitMQ:选择哪个作为微服务消息代理? - otonomo
  50. Java Stream和Collection比较:何时以及如何从Java API返回Stream而不是集合Collection? - TomaszKiełbowicz
  51. 如何在SpringBoot中使用Hibernate @NaturalId?
  52. RPC框架设计----NIO编程通道(Channel)
  53. The use of jquery
  54. Redis, Kafka or rabbitmq: which one to choose as the micro service message broker- otonomo
  55. Comparison of Java stream and collection: when and how to return stream instead of collection from Java API- TomaszKie ł bowicz
  56. How to use hibernate @ naturalid in springboot?
  57. RPC framework design -- NiO programming channel
  58. RPC框架设计----NIO编程Selector (选择器)
  59. RPC framework design -- NiO programming selector
  60. Linux centos重启命令