簡體   English   中英

如何在多個 SpringBootTest 之間復用 Testcontainer?

[英]How to reuse Testcontainers between multiple SpringBootTests?

我正在使用帶有 Spring Boot 的 TestContainers 來為這樣的存儲庫運行單元測試:

@Testcontainers
@ExtendWith(SpringExtension.class)
@ActiveProfiles("itest")
@SpringBootTest(classes = RouteTestingCheapRouteDetector.class)
@ContextConfiguration(initializers = AlwaysFailingRouteRepositoryShould.Initializer.class)
@TestExecutionListeners(listeners = DependencyInjectionTestExecutionListener.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Tag("docker")
@Tag("database")
class AlwaysFailingRouteRepositoryShould {

  @SuppressWarnings("rawtypes")
  @Container
  private static final PostgreSQLContainer database =
      new PostgreSQLContainer("postgres:9.6")
          .withDatabaseName("database")
          .withUsername("postgres")
          .withPassword("postgres");

但是現在我有 14 個這樣的測試,每次運行測試時都會啟動一個新的 Postgres 實例。 是否可以在所有測試中重復使用同一個實例? Singleton 模式沒有幫助,因為每個測試都會啟動一個新的應用程序。

我還在.testcontainers.properties.withReuse(true)中嘗試過testcontainers.reuse.enable=true ,但這沒有幫助。

如果您想擁有可重復使用的容器,則不能使用 JUnit Jupiter 注釋@Container 此注釋確保在每次測試后停止容器。

您需要的是 singleton 容器方法,並使用例如@BeforeAll來啟動您的容器。 即使您隨后在多個測試中使用了 .start(),如果您在容器定義中同時使用.start() .withReuse(true)和您家中的以下.testcontainers.properties文件選擇可重用性,Testcontainers 也不會啟動新容器目錄:

testcontainers.reuse.enable=true

一個簡單的示例可能如下所示:

@SpringBootTest
public class SomeIT {

  public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
    withReuse(true);

  @BeforeAll
  public static void beforeAll() {
    postgreSQLContainer.start();
  }

  @Test
  public void test() {

  }

}

和另一個集成測試:

@SpringBootTest
public class SecondIT {

  public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
    withReuse(true);

  @BeforeAll
  public static void beforeAll() {
    postgreSQLContainer.start();
  }

  @Test
  public void secondTest() {

  }

}

目前有一個PR 增加了關於這個的文檔

我整理了一篇博文,詳細解釋了如何使用 Testcontainers 重用容器

If you decide go forward with the singleton pattern , mind the warning in " Database containers launched via JDBC URL scheme ". 我花了幾個小時才注意到,即使我使用的是singleton 模式,總是會創建一個額外的容器,映射到不同的端口。

In summary, do not use the test containers JDBC (host-less) URIs, such as jdbc:tc:postgresql:<image-tag>:///<databasename> , if you need use the singleton pattern .

接受的答案很好,但問題是您仍然必須為每個集成測試重復配置(創建、啟動等)。 最好使用更少的代碼行進行更簡單的配置。 我認為更干凈的版本會使用 JUnit 5 個擴展。

這就是我解決問題的方法。 下面的示例使用 MariaDB 容器,但該概念適用於所有容器。

  1. 創建包含 class 的容器配置:
public class AppMariaDBContainer extends MariaDBContainer<AppMariaDBContainer> {

    private static final String IMAGE_VERSION = "mariadb:10.5";
    private static final String DATABASE_NAME = "my-db";
    private static final String USERNAME = "user";
    private static final String PASSWORD = "strong-password";

    public static AppMariaDBContainer container = new AppMariaDBContainer()
            .withDatabaseName(DATABASE_NAME)
            .withUsername(USERNAME)
            .withPassword(PASSWORD);

    public AppMariaDBContainer() {
        super(IMAGE_VERSION);
    }

}
  1. 創建一個擴展 class 來啟動容器並設置DataSource屬性。 並在需要時運行遷移:
public class DatabaseSetupExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) {
        AppMariaDBContainer.container.start();
        updateDataSourceProps(AppMariaDBContainer.container);
        //migration logic here (if needed)
    }

    private void updateDataSourceProps(AppMariaDBContainer container) {
        System.setProperty("spring.datasource.url", container.getJdbcUrl());
        System.setProperty("spring.datasource.username", container.getUsername());
        System.setProperty("spring.datasource.password", container.getPassword());
    }

}
  1. 添加@ExtendWith到你的測試 class
@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class ApplicationIntegrationTests {

    @Test
    void someTest() {
    }

}

另一個測試

@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class AnotherIntegrationTests {

    @Test
    void anotherTest() {
    }

}

使用 singleton 容器或可重用容器是可能的解決方案,但因為它們不是 scope 容器的生命周期與應用程序上下文的生命周期都不太理想。

但是,可以使用ContextCustomizerFactory將容器 scope 到應用程序上下文生命周期, 我已經在博客文章中對此進行了更詳細的介紹

在測試中使用:

@Slf4j
@SpringBootTest
@EnabledPostgresTestContainer
class DemoApplicationTest {

    @Test
    void contextLoads() {
        log.info("Hello world");
    }

}

然后在META-INF/spring.factories中啟用注解:

org.springframework.test.context.ContextCustomizerFactory=\
  com.logarithmicwhale.demo.EnablePostgresTestContainerContextCustomizerFactory

可以實現為:

public class EnablePostgresTestContainerContextCustomizerFactory implements ContextCustomizerFactory {

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface EnabledPostgresTestContainer {
    }

    @Override
    public ContextCustomizer createContextCustomizer(Class<?> testClass,
            List<ContextConfigurationAttributes> configAttributes) {
        if (!(AnnotatedElementUtils.hasAnnotation(testClass, EnabledPostgresTestContainer.class))) {
            return null;
        }
        return new PostgresTestContainerContextCustomizer();
    }

    @EqualsAndHashCode // See ContextCustomizer java doc
    private static class PostgresTestContainerContextCustomizer implements ContextCustomizer {

        private static final DockerImageName image = DockerImageName
                .parse("postgres")
                .withTag("14.1");

        @Override
        public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
            var postgresContainer = new PostgreSQLContainer<>(image);
            postgresContainer.start();
            var properties = Map.<String, Object>of(
                    "spring.datasource.url", postgresContainer.getJdbcUrl(),
                    "spring.datasource.username", postgresContainer.getUsername(),
                    "spring.datasource.password", postgresContainer.getPassword(),
                    // Prevent any in memory db from replacing the data source
                    // See @AutoConfigureTestDatabase
                    "spring.test.database.replace", "NONE"
            );
            var propertySource = new MapPropertySource("PostgresContainer Test Properties", properties);
            context.getEnvironment().getPropertySources().addFirst(propertySource);
        }

    }

}

我不確定@Testcontainers是如何工作的,但我懷疑它可能會根據 class 工作。

Just make your singleton static as described in Singleton pattern and get it in every test from your signleton holder, don't define it in every test class.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM