Spring @Sql Annotations,可以在所有测试之前运行一次吗?

Posted

技术标签:

【中文标题】Spring @Sql Annotations,可以在所有测试之前运行一次吗?【英文标题】:Spring @Sql Annotations, possible to run once before all tests? 【发布时间】:2021-10-06 10:08:11 【问题描述】:

使用 Spring 进行集成测试,我可以像这样填充运行脚本的测试数据库...

@Test
@Sql("/db/schema.sql", "/db/accountConfig.sql", "/db/functions/fnSomething.sql")
public void verifySomething() 
   ...

但是,我希望在运行任何测试之前只运行一次我的所有 .sql 文件。有没有 JUnit 4 的方法来做到这一点?似乎 @Sql 仅针对带有 @Test 注释的方法运行。

我正在使用 Junit 4、Spring Boot、Java 15、Testcontainers。

我尝试过的事情......

我尝试在我的测试类扩展的类上使用@BeforeClass,但它似乎在我的测试之后运行。 Testcontainers 确实有一个初始化脚本函数,但它只需要一个文件,并不理想。 我也尝试过ScriptUtils.executeSqlScript,但由于某种原因,测试容器不喜欢这样。

这是我的示例代码,适用于@Sql,但不适用于ScriptUtils.executeSqlScript

@ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
public abstract class AbstractIntegrationTest 

    @ClassRule 
    public static MSSQLServerContainer mssqlserver = new MSSQLServerContainer();

  
    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> 

        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) 
            ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
            Properties props = new Properties();
            props.put("spring.datasource.driver-class-name", mssqlserver.getDriverClassName());
            props.put("spring.datasource.url", mssqlserver.getJdbcUrl());
            props.put("spring.datasource.username", mssqlserver.getUsername());
            props.put("spring.datasource.password", mssqlserver.getPassword());

            environment
                .getPropertySources()
                .addFirst(new PropertiesPropertySource("myTestDBProps", props));      

            configurableApplicationContext.setEnvironment(environment);
    

我的测试类只是扩展AbstractIntegrationTest。但是使用@Sql 会为每个测试用例运行脚本。有没有人有更好的方法来初始化 SQL 脚本的建议?尝试过flyway,但它不允许从脚本创建数据库。

【问题讨论】:

Liquibase 非常适合设置和管理测试数据库数据集。 docs.liquibase.com/tools-integrations/springboot/… 【参考方案1】:

我建议查看在 Spring 上下文初始化时执行一次 SQL 脚本的数据库初始化程序 bean。基本上,根据您使用的是 JDBC 还是 R2DBC,有两种解决方案。由于您要初始化多个脚本,因此您应该使用CompositeDatabasePopulator。还要记住导入正确的类,因为它们具有相同的名称但来自不同的包,同样取决于 JDBC/R2DBC。

要从resources 文件夹加载资源,请随意使用以下任一选项:

Resource resource = new ClassPathResource("sql/schema.sql")) @Value("classpath:sql/schema.sql") Resource resource

这个解决方案相当灵活,因为您可以使用 @TestConfiguration 为测试上下文定义初始化器 bean(请记住使用此注解有点棘手,所以我建议您参考这篇文章:Quirks of Spring's @TestConfiguration,这对我有很大帮助) .

该解决方案应该适用于任何具有ConnectionFactory 可用的解决方案,包括Test Containers。

JDBC

使用org.springframework.jdbc.datasource.init.CompositeDatabasePopulator 使用org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

通过调用populate 进行的初始化必须在@PostConstuct 中进行,因为Spring Boot 不会自动检测填充器。我建议将以下代码 sn-p 包装在一个配置类中,并将其包含在测试范围内。

@Autowired
private DataSource dataSource;

@PostConstruct
public void initData() throws SQLException 

    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/schema.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/catalog.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/data.sql")));

    populator.populate(dataSource.getConnection());

R2DBC

使用org.springframework.r2dbc.connection.init.CompositeDatabasePopulator 使用org.springframework.r2dbc.connection.init.ResourceDatabasePopulator

您可以使用ConnectionFactoryInitializer 在加载 Spring 上下文时初始化添加到此初始化程序的所有填充器。

@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) 

    var populator = new CompositeDatabasePopulator();
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/schema.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/catalog.sql")));
    populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("sql/data.sql")));


    var initializer = new ConnectionFactoryInitializer();
    initializer.setConnectionFactory(connectionFactory);
    initializer.setDatabasePopulator(populator);

    return initializer;

【讨论】:

我不知道这些,感谢您的建议,我会尝试一下。 祝你好运。设置一个抽象的父级有点棘手,并且与 Spring 上下文的连接需要进行很多试验。我希望我的回答至少能给你一个好的开始。我使用带有@Testcontainers 注释的抽象类和带有@Container 注释的静态容器。在同一个类中,我使用@DynamicPropertySource 将容器数据库配置注入属性。【参考方案2】:

您可以使用@Sql 注释您的测试类。然后它应该在整个测试中只执行一次。然后也可以在方法级别上进行个别调整。

【讨论】:

以上是关于Spring @Sql Annotations,可以在所有测试之前运行一次吗?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot Annotations 注解

在 Grails 中使用 Pre/Post Spring-Security Annotations

以编程方式启用 Spring Security pre-post-annotations

Spring Util:Properties Injection via Annotations into a bean

[转]what’s the difference between @Component ,@Repository & @Service annotations in Spring

What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段