拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Spring @DynamicPropertySource指南

Spring @DynamicPropertySource指南

白鹭 - 2021-11-24 556 0 0

1.概述

当今的应用程序并不是孤立存在的:我们通常需要连接到各种外部组件,例如PostgreSQL,Apache Kafka,Cassandra,Redis和其他外部API。

在本教程中,我们将了解Spring Framework 5.2.5如何通过引入动态属性来促进测试此类应用程序。

首先,我们将从定义问题开始,然后看看我们过去如何以不太理想的方式解决问题。然后,我们将介绍@DynamicPropertySource批注,并查看它如何为解决同一问题提供更好的解决方案。最后,我们还将介绍测试框架中的另一个解决方案,该解决方案比纯Spring解决方案要优越。

2.问题:动态属性

假设我们正在开发一个使用PostgreSQL作为其数据库的典型应用程序。我们将从一个简单的JPA实体开始:

@Entity

 @Table(name = "articles")

 public class Article {



 @Id

 @GeneratedValue(strategy = IDENTITY)

 private Long id;



 private String title;



 private String content;



 // getters and setters

 }

为确保该实体按预期工作,我们应该为其编写测试以验证其数据库交互。由于此测试需要与真实数据库进行对话,因此我们应该事先设置一个PostgreSQL实例。

在测试执行过程中,可以使用不同的方法来设置此类基础结构工具。实际上,此类解决方案主要分为三类:

  • 在某处设置单独的数据库服务器以进行测试
  • 使用一些轻量级的,特定于测试的替代品,例如H2
  • 让测试本身管理数据库的生命周期

由于我们不应该区分测试环境和生产环境,因此与使用诸如H2之类的测试倍数相比,有更好的选择。除了使用真实数据库外,第三个选项还为测试提供了更好的隔离。而且,借助Docker和Testcontainers之类的技术,很容易实现第三个选项。

如果使用诸如Testcontainers之类的技术,则我们的测试工作流程将如下所示:

  1. 在进行所有测试之前,请先设置一个组件,例如PostgreSQL。通常,这些组件侦听随机端口。
  2. 运行测试。
  3. 拆下组件。

如果我们的PostgreSQL容器每次都会监听一个随机端口,那么我们应该以某种方式spring.datasource.url配置属性。基本上,每个测试都应具有该配置属性的自己的版本。

当配置是静态的时,我们可以使用Spring Boot的配置管理工具轻松地管理它们。但是,当我们面对动态配置时,同一任务可能会充满挑战。

现在我们知道了问题所在,让我们看一下传统的解决方案。

3.传统解决方案

实现动态属性的第一种方法是使用自定义ApplicationContextInitializer 。基本上,我们首先建立基础架构,并使用第一步中的信息来自定义ApplicationContext

@SpringBootTest

 @Testcontainers

 @ContextConfiguration(initializers = ArticleTraditionalLiveTest.EnvInitializer.class)

 class ArticleTraditionalLiveTest {



 @Container

 static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:11")

 .withDatabaseName("prop")

 .withUsername("postgres")

 .withPassword("pass")

 .withExposedPorts(5432);



 static class EnvInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {



 @Override

 public void initialize(ConfigurableApplicationContext applicationContext) {

 TestPropertyValues.of(

 String.format("spring.datasource.url=jdbc:postgresql://localhost:%d/prop", postgres.getFirstMappedPort()),

 "spring.datasource.username=postgres",

 "spring.datasource.password=pass"

 ).applyTo(applicationContext);

 }

 }



 // omitted

 }

让我们来看一下这个有点复杂的设置。 JUnit将先创建并启动容器。容器准备好后,Spring扩展将调用初始化程序以将动态配置应用于Spring Environment显然,这种方法有点冗长和复杂。

只有完成以下步骤,我们才能编写测试:

@Autowired

 private ArticleRepository articleRepository;



 @Test

 void givenAnArticle_whenPersisted_thenShouldBeAbleToReadIt() {

 Article article = new Article();

 article.setTitle("A Guide to @DynamicPropertySource in Spring");

 article.setContent("Today's applications...");



 articleRepository.save(article);



 Article persisted = articleRepository.findAll().get(0);

 assertThat(persisted.getId()).isNotNull();

 assertThat(persisted.getTitle()).isEqualTo("A Guide to @DynamicPropertySource in Spring");

 assertThat(persisted.getContent()).isEqualTo("Today's applications...");

 }

4. @DynamicPropertySource

Spring Framework 5.2.5引入了@DynamicPropertySource批注,以方便添加具有动态值的属性。我们要做的就是创建一个以@DynamicPropertySource注释的静态方法,并仅将单个DynamicPropertyRegistry实例作为输入:

@SpringBootTest

 @Testcontainers

 public class ArticleLiveTest {



 @Container

 static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:11")

 .withDatabaseName("prop")

 .withUsername("postgres")

 .withPassword("pass")

 .withExposedPorts(5432);



 @DynamicPropertySource

 static void registerPgProperties(DynamicPropertyRegistry registry) {

 registry.add("spring.datasource.url",

 () -> String.format("jdbc:postgresql://localhost:%d/prop", postgres.getFirstMappedPort()));

 registry.add("spring.datasource.username", () -> "postgres");

 registry.add("spring.datasource.password", () -> "pass");

 }



 // tests are same as before

 }

如上所示,我们在给定的DynamicPropertyRegistry add(String, Supplier<Object>) Environment添加一些属性。与我们之前看到的初始化程序相比,这种方法更加简洁。请注意,使用@DynamicPropertySource注释的方法必须声明为static DynamicPropertyRegistry类型的一个参数。

@DynmicPropertySource批注背后的主要动机是为了更轻松地促进已经可能的事情。尽管最初设计它是为了与Testcontainer一起使用,但是可以在需要使用动态配置的任何地方使用它。

5.另一种选择:测试夹具设施

到目前为止,在这两种方法中,夹具设置和测试代码都紧密地交织在一起。有时,两个关注点之间的紧密联系使测试代码复杂化,尤其是当我们需要设置多个内容时。想象一下,如果我们在单个测试中使用PostgreSQL和Apache Kafka,基础结构设置将是什么样。

除此之外,基础架构设置和应用动态配置将在需要它们的所有测试中重复进行

为了避免这些弊端,我们可以使用大多数测试框架提供的测试夹具设施。例如,在JUnit 5中,我们可以定义一个扩展,该扩展在所有测试之前启动PostgreSQL实例,配置Spring Boot,并在运行测试后停止PostgreSQL实例:

public class PostgreSQLExtension implements BeforeAllCallback, AfterAllCallback {



 private PostgreSQLContainer<?> postgres;



 @Override

 public void beforeAll(ExtensionContext context) {

 postgres = new PostgreSQLContainer<>("postgres:11")

 .withDatabaseName("prop")

 .withUsername("postgres")

 .withPassword("pass")

 .withExposedPorts(5432);



 postgres.start();

 String jdbcUrl = String.format("jdbc:postgresql://localhost:%d/prop", postgres.getFirstMappedPort());

 System.setProperty("spring.datasource.url", jdbcUrl);

 System.setProperty("spring.datasource.username", "postgres");

 System.setProperty("spring.datasource.password", "pass");

 }



 @Override

 public void afterAll(ExtensionContext context) {

 postgres.stop();

 }

 }

在这里,我们正在实现AfterAllCallbackBeforeAllCallback来创建JUnit 5扩展。这样,JUnit 5将在运行所有测试之前beforeAll()逻辑,并在运行测试之后执行afterAll()使用这种方法,我们的测试代码将变得干净:

@SpringBootTest

 @ExtendWith(PostgreSQLExtension.class)

 public class ArticleTestFixtureLiveTest {

 // just the test code

 }

除了更具可读性之外,我们只需添加@ExtendWith(PostgreSQLExtension.class)批注即可轻松重用相同的功能。不需要像其他两种方法那样在需要的任何地方复制粘贴整个PostgreSQL设置。

六,结论

在本教程中,我们首先看到测试依赖于数据库之类的Spring组件有多困难。然后,我们针对此问题引入了三种解决方案,每种解决方案都在先前解决方案必须提供的基础上进行了改进。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *