Java Spring';s测试注释@Sql的行为是否与@BeforeClass类似?

Java Spring';s测试注释@Sql的行为是否与@BeforeClass类似?,java,spring,junit,spring-test,Java,Spring,Junit,Spring Test,我如何告诉@Sql注释只为类运行一次,而不是为每个@Test方法运行一次 喜欢与课前的行为相同吗 @org.springframework.test.context.jdbc.Sql( scripts = "classpath:schema-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD ) public class TestClass { @Test public v

我如何告诉
@Sql
注释只为类运行一次,而不是为每个
@Test
方法运行一次

喜欢与课前的行为相同吗

@org.springframework.test.context.jdbc.Sql(
     scripts = "classpath:schema-test.sql",
     executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
public class TestClass {
      @Test
      public void test1() {
        //runs the @Sql script
      }

      @Test
      public void test2() {
        //runs the @Sql script again
      }
}

你不能开箱即用。唯一的方法是在测试方法之前和测试方法之后

负责执行这些脚本的侦听器不实现类之前或之后的方法


为了解决这个问题,我将实现自己的,包装默认的
sqlscriptstexecutionListener
。然后,您可以在测试中声明使用新的侦听器而不是旧的侦听器

public class BeforeClassSqlScriptsTestExecutionListener implements TestExecutionListener
{    
    @Override
    public void beforeTestClass(final TestContext testContext) throws Exception
    {
        // Note, we're deliberately calling beforeTest*Method*
        new SqlScriptsTestExecutionListener().beforeTestMethod(testContext);
    }

    @Override
    public void prepareTestInstance(final TestContext testContext) { }

    @Override
    public void beforeTestMethod(final TestContext testContext) { }

    @Override
    public void afterTestMethod(final TestContext testContext) { }

    @Override
    public void afterTestClass(final TestContext testContext) { }
}
您的测试将变成:

@TestExecutionListeners(
    listeners = { BeforeClassSqlScriptsTestExecutionListener.class },
    /* Here, we're replacing more than just SqlScriptsTestExecutionListener, so manually
       include any of the default above if they're still needed: */
    mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS
)
@org.springframework.test.context.jdbc.Sql(
    scripts = "classpath:schema-test.sql",
    executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
public class MyTest
{
    @Test
    public void test1() { }

    @Test
    public void test2() { }
}

由于
DefaultTestContext.java
中的
getTestMethod()
方法,此代码引发
IllegalStateException
(Spring 5.0.1):

public final Method getTestMethod() {
    Method testMethod = this.testMethod;
    Assert.state(testMethod != null, "No test method");
    return testMethod;
}
通过建议的实现调用
beforeTestClass
方法时,
textContext
不包含有效的
testMethod
(这在本阶段是正常的):

执行负责运行SQL脚本的代码(在
sqlscriptstexecutionListener
中)时,需要有效的
testMethod

Set<Sql> sqlAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(
            testContext.getTestMethod(), Sql.class, SqlGroup.class);

对于JUnit 5,直接的清洁解决方案:

但是,如果您的数据库连接未配置为
autoCommit=true
,则必须将所有内容包装到事务中:

@RootInMemoryDbConfig
@Slf4j
class SomeServiceTest {
    @BeforeAll
    void setup(@Autowired DataSource dataSource,
            @Autowired PlatformTransactionManager transactionManager) {
        new TransactionTemplate(transactionManager).execute((ts) -> {
            try (Connection conn = dataSource.getConnection()) {
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("appconfig.sql"));
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("album.sql"));
                // should work without manually commit but didn't for me (because of using AUTOCOMMIT=OFF)
                // I use url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1;MODE=MySQL;AUTOCOMMIT=OFF
                // same will happen with DataSourceInitializer & DatabasePopulator (at least with this setup)
                conn.commit();
            } catch (SQLException e) {
                SomeServiceTest.log.error(e.getMessage(), e);
            }
            return null;
        });
    }
    // your @Test methods follow ...
为什么清洁溶液

因为根据:

@Sql和@SqlConfig提供的配置选项是 与ScriptUtils和 ResourceDatabasePopulator,但是 XML名称空间元素

奖金


您可以将此方法与其他@Sql声明混合使用。

对于JUnit5,我支持ADRCC的解决方案

对于Junit 4,您可以执行以下操作:

@Autowired
private DataSource database;

private static boolean dataLoaded = false;

    @Before
    public void setup() throws SQLException {
        if(!dataLoaded) {
            try (Connection con = database.getConnection()) {
                ScriptUtils.executeSqlScript(con, new ClassPathResource("path/to/script.sql"));
                dataLoaded = true;
            }
        }
    }
(同样,假设您的连接具有
autoCommit=true
,请参阅ADRCC的文章。)


如果要并行运行测试,则需要同步方法。

@BeforeAll和@BeforeClass注释需要“静态”方法。所以它不起作用。 配置文件中的@PostConstruct如何?这对我很管用

@TestConfiguration
public class IntegrationTestConfiguration {

@Autowired
private DataSource dataSource;

@PostConstruct
public void initDB() throws SQLException {
    try (Connection con = dataSource.getConnection()) {
        ScriptUtils.executeSqlScript(con, new ClassPathResource("data.sql"));
    }
}
}

@ContextConfiguration(classes = {IntegrationTestConfiguration.class})
public class YourIntegrationTest {

}

(这是未经测试的,因为我没有Spring上下文或数据库来测试它,但我相信它应该可以工作)您是否只对Junit的最新版本的响应感兴趣。谢谢!这是一个非常简单的解决方案。。。这对我很有效!
@RootInMemoryDbConfig
@Slf4j
class SomeServiceTest {
    @BeforeAll
    void setup(@Autowired DataSource dataSource,
            @Autowired PlatformTransactionManager transactionManager) {
        new TransactionTemplate(transactionManager).execute((ts) -> {
            try (Connection conn = dataSource.getConnection()) {
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("appconfig.sql"));
                ScriptUtils.executeSqlScript(conn, new ClassPathResource("album.sql"));
                // should work without manually commit but didn't for me (because of using AUTOCOMMIT=OFF)
                // I use url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1;MODE=MySQL;AUTOCOMMIT=OFF
                // same will happen with DataSourceInitializer & DatabasePopulator (at least with this setup)
                conn.commit();
            } catch (SQLException e) {
                SomeServiceTest.log.error(e.getMessage(), e);
            }
            return null;
        });
    }
    // your @Test methods follow ...
@Autowired
private DataSource database;

private static boolean dataLoaded = false;

    @Before
    public void setup() throws SQLException {
        if(!dataLoaded) {
            try (Connection con = database.getConnection()) {
                ScriptUtils.executeSqlScript(con, new ClassPathResource("path/to/script.sql"));
                dataLoaded = true;
            }
        }
    }
@TestConfiguration
public class IntegrationTestConfiguration {

@Autowired
private DataSource dataSource;

@PostConstruct
public void initDB() throws SQLException {
    try (Connection con = dataSource.getConnection()) {
        ScriptUtils.executeSqlScript(con, new ClassPathResource("data.sql"));
    }
}
}

@ContextConfiguration(classes = {IntegrationTestConfiguration.class})
public class YourIntegrationTest {

}