【问题标题】:How can Spring's test annotation @Sql behave like @BeforeClass?Spring 的测试注解@Sql 如何表现得像@BeforeClass?
【发布时间】:2018-05-26 07:29:24
【问题描述】:

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

喜欢和@BeforeClass有同样的行为吗?

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

【问题讨论】:

  • 您是只对 Junit
  • 始终是最新版本。

标签: java spring junit spring-test


【解决方案1】:

对于 JUnit 5,直接clean 解决方案

@MyInMemoryDbConfig
//@Sql(value = {"/appconfig.sql", "/album.sql"}) -> code below is equivalent but at class level
class SomeServiceTest {
    @BeforeAll
    void setup(@Autowired DataSource dataSource) {
        try (Connection conn = dataSource.getConnection()) {
            // you'll have to make sure conn.autoCommit = true (default for e.g. H2)
            // e.g. url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1;MODE=MySQL
            ScriptUtils.executeSqlScript(conn, new ClassPathResource("appconfig.sql"));
            ScriptUtils.executeSqlScript(conn, new ClassPathResource("album.sql"));
        }
    }
    // your @Test methods follow ...

但是当你的数据库连接没有配置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 ...

为什么要干净 解决方案

因为根据Script Configuration with @SqlConfig

@Sql 和@SqlConfig 提供的配置选项是 等同于 ScriptUtils 和 ResourceDatabasePopulator 是由 XML 命名空间元素。

奖金

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

【讨论】:

  • 谢谢!这是一个足够简单的解决方案……对我来说效果很好!
【解决方案2】:

您不能开箱即用。 @Sql annotation 只有 two modes - BEFORE_TEST_METHODAFTER_TEST_METHOD

负责执行这些脚本的侦听器SqlScriptsTestExecutionListener 没有实现类前或类后方法。


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

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() { }
}

【讨论】:

  • (这是未经测试的,因为我没有 Spring 上下文或数据库来测试它,但我相信它应该可以工作)
  • 一个好主意!我确实尝试过,但不幸的是,由于 SqlScriptsTestExecutionListener 中的细节需要 TEST_METHOD 存在,它失败了。它不在 beforeClass 级别,因此它失败了。同样的方法是在侦听器中直接使用 ScriptUtils.executeSqlScript 而不是委托给 SqlScriptsTestExecutionListener - 对@adrhc 答案的一点扩展
【解决方案3】:

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

}

【讨论】:

    【解决方案4】:

    对于 JUnit 5,我支持 adrhc 的解决方案。

    对于 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,请参阅 adrhc 的帖子。)

    如果您打算并行运行测试,那么您需要使方法同步。

    【讨论】:

      【解决方案5】:

      由于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(现阶段正常):

      public class BeforeClassSqlScriptsTestExecutionListener implements TestExecutionListener {
      
          @Override
          public void beforeTestClass(TestContext testContext) throws Exception {
              new SqlScriptsTestExecutionListener().beforeTestMethod(testContext);
          }
      }
      

      当负责运行 SQL 脚本的代码(在SqlScriptsTestExecutionListener 中)被执行时,需要一个有效的testMethod

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

      我最终使用了这个解决方法:

      @Before
      public void setUp() {
          // Manually initialize DB as @Sql annotation doesn't support class-level execution phase (actually executed before every test method)
          // See https://jira.spring.io/browse/SPR-14357
          if (!dbInitialized) {
              final ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator();
              resourceDatabasePopulator.addScript(new ClassPathResource("/sql/[...].sql"));
              resourceDatabasePopulator.execute(dataSource);
              dbInitialized = true;
          }
          [...]
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-11
        • 1970-01-01
        • 1970-01-01
        • 2021-07-08
        • 2015-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多