2016-10-24 12 views
3

Я использую Liquibase в своем веб-приложении Spring. У меня есть группа объектов с сотнями тестов для API REST в тестах интеграции для каждого объекта, такого как User, Account, Invoice, License и т. Д. Все мои тесты интеграции проходят при запуске по классу, но многие из них терпят неудачу при совместном использовании gradle test. Скорее всего, есть столкновение данных между тестами, и я не заинтересован в том, чтобы тратить время на исправление очистки данных на данный момент. Я предпочитаю отбрасывать БД и контекст после каждого класса. Я решил, что могу использовать @DirtiesContext в классе, и поэтому я аннотировал его с помощью теста.Как сбросить память h2db в памяти между тестами интеграции Spring?

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = {Application.class, SecurityConfiguration.class}, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 
@DirtiesContext 
public class InvoiceResourceIntTest { 

Я вижу, что после добавления аннотаций, контекст веб-приложение запускается для каждого класса, но при инициализации LiquiBase происходит, запросы не выполняются, так как контрольная сумма матчей. Поскольку это БД в памяти, я ожидал, что БД будет уничтожена вместе с весенним контекстом, но этого не происходит.

Я также установил jpa hibernate ddl-auto в create-drop, но это не помогло. Следующая опция, которую я рассматриваю, это вместо mem, напишите h2db в файл и удалите этот файл в @BeforeClass моих файлов тестовых файлов интеграции. Я предпочитаю автоматически отбрасывать db в памяти вместо управления им в тесте, но хочу попробовать здесь как последний вариант. Спасибо за помощь.

Update:

Я обновил тест, как показано ниже.

@RunWith(SpringRunner.class) 
@SpringBootTest(classes = {Application.class, SecurityConfiguration.class}, 
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 
    properties = "spring.datasource.name=AccountResource") 
@DirtiesContext 
public class AccountResourceIntTest { 

У меня есть уникальные имена для каждого теста интеграции. Я до сих пор не вижу, что база данных является новой, потому что я вижу только контрольную сумму Liquibase в журналах.

Вот мое приложение конфигурации из application.yml

spring: 
    datasource: 
     driver-class-name: org.h2.Driver 
     url: jdbc:h2:mem:myApp;DB_CLOSE_DELAY=-1 
     name: 
     username: 
     password: 
    jpa: 
     database-platform: com.neustar.registry.le.domain.util.FixedH2Dialect 
     database: H2 
     open-in-view: false 
     show_sql: true 
     hibernate: 
      ddl-auto: create-drop 
      naming-strategy: org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy 
     properties: 
      hibernate.cache.use_second_level_cache: false 
      hibernate.cache.use_query_cache: false 
      hibernate.generate_statistics: true 
      hibernate.hbm2ddl.auto: validate 

Мой проект генерируется из JHipster версии 2.x, если это имеет значение. См. Ниже мой класс конфигурации базы данных. AppProperties - это специфичные для приложения свойства (в отличие от Spring).

@Configuration 
public class DatabaseConfiguration { 

    private static final int LIQUIBASE_POOL_INIT_SIZE = 1; 
    private static final int LIQUIBASE_POOL_MAX_ACTIVE = 1; 
    private static final int LIQUIBASE_POOL_MAX_IDLE = 0; 
    private static final int LIQUIBASE_POOL_MIN_IDLE = 0; 

    private static final Logger LOG = LoggerFactory.getLogger(DatabaseConfiguration.class); 

    /** 
    * Creates data source. 
    * 
    * @param dataSourceProperties Data source properties configured. 
    * @param appProperties the app properties 
    * @return Data source. 
    */ 
    @Bean(destroyMethod = "close") 
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) 
    @Primary 
    public DataSource dataSource(final DataSourceProperties dataSourceProperties, 
     final AppProperties appProperties) { 

     LOG.info("Configuring Datasource with url: {}, user: {}", 
      dataSourceProperties.getUrl(), dataSourceProperties.getUsername()); 

     if (dataSourceProperties.getUrl() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database URL!"); 
      throw new ApplicationContextException("Data source is not configured correctly, please specify URL"); 
     } 
     if (dataSourceProperties.getUsername() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database user!"); 
      throw new ApplicationContextException(
       "Data source is not configured correctly, please specify database user"); 
     } 
     if (dataSourceProperties.getPassword() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database password!"); 
      throw new ApplicationContextException(
       "Data source is not configured correctly, " 
        + "please specify database password"); 
     } 

     PoolProperties config = new PoolProperties(); 
     config.setDriverClassName(dataSourceProperties.getDriverClassName()); 
     config.setUrl(dataSourceProperties.getUrl()); 
     config.setUsername(dataSourceProperties.getUsername()); 
     config.setPassword(dataSourceProperties.getPassword()); 
     config.setInitialSize(appProperties.getDatasource().getInitialSize()); 
     config.setMaxActive(appProperties.getDatasource().getMaxActive()); 
     config.setTestOnBorrow(appProperties.getDatasource().isTestOnBorrow()); 
     config.setValidationQuery(appProperties.getDatasource().getValidationQuery()); 

     org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(config); 
     LOG.info("Data source is created: {}", dataSource); 

     return dataSource; 

    } 

    /** 
    * Create data source for Liquibase using dba user and password provided for "liquibase" 
    * in application.yml. 
    * 
    * @param dataSourceProperties Data source properties 
    * @param liquibaseProperties Liquibase properties. 
    * @param appProperties the app properties 
    * @return Data source for liquibase. 
    */ 
    @Bean(destroyMethod = "close") 
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) 
    public DataSource liquibaseDataSource(final DataSourceProperties dataSourceProperties, 
     final LiquibaseProperties liquibaseProperties, final AppProperties appProperties) { 

     LOG.info("Configuring Liquibase Datasource with url: {}, user: {}", 
      dataSourceProperties.getUrl(), liquibaseProperties.getUser()); 

     /* 
     * This is needed for integration testing. When we run integration tests using SpringJUnit4ClassRunner, Spring 
     * uses 
     * H2DB if it is in the class path. In that case, we have to create pool for H2DB. 
     * Need to find a better solution for this. 
     */ 
     if (dataSourceProperties.getDriverClassName() != null 
      && dataSourceProperties.getDriverClassName().startsWith("org.h2.")) { 
      return dataSource(dataSourceProperties, appProperties); 
     } 

     if (dataSourceProperties.getUrl() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database URL!"); 
      throw new ApplicationContextException("Liquibase is not configured correctly, please specify URL"); 
     } 
     if (liquibaseProperties.getUser() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database user!"); 
      throw new ApplicationContextException(
       "Liquibase is not configured correctly, please specify database user"); 
     } 
     if (liquibaseProperties.getPassword() == null) { 
      LOG.error("Your Liquibase configuration is incorrect, please specify database password!"); 
      throw new ApplicationContextException(
       "Liquibase is not configured correctly, please specify database password"); 
     } 

     PoolProperties config = new PoolProperties(); 

     config.setDriverClassName(dataSourceProperties.getDriverClassName()); 
     config.setUrl(dataSourceProperties.getUrl()); 
     config.setUsername(liquibaseProperties.getUser()); 
     config.setPassword(liquibaseProperties.getPassword()); 

     // for liquibase pool, we dont need more than 1 connection 
     config.setInitialSize(LIQUIBASE_POOL_INIT_SIZE); 
     config.setMaxActive(LIQUIBASE_POOL_MAX_ACTIVE); 

     // for liquibase pool, we dont want any connections to linger around 
     config.setMaxIdle(LIQUIBASE_POOL_MAX_IDLE); 
     config.setMinIdle(LIQUIBASE_POOL_MIN_IDLE); 

     org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource(config); 
     LOG.info("Liquibase data source is created: {}", dataSource); 

     return dataSource; 

    } 

    /** 
    * Creates a liquibase instance. 
    * 
    * @param dataSource Data source to use for liquibase. 
    * @param dataSourceProperties Datasource properties. 
    * @param liquibaseProperties Liquibase properties. 
    * @return Liquibase instance to be used in spring. 
    */ 
    @Bean 
    public SpringLiquibase liquibase(@Qualifier("liquibaseDataSource") final DataSource dataSource, 
     final DataSourceProperties dataSourceProperties, final LiquibaseProperties liquibaseProperties) { 

     // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously 
     SpringLiquibase liquibase = new AsyncSpringLiquibase(); 
     liquibase.setDataSource(dataSource); 
     liquibase.setChangeLog("classpath:config/liquibase/master.xml"); 
     liquibase.setContexts(liquibaseProperties.getContexts()); 
     liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); 
     liquibase.setDropFirst(liquibaseProperties.isDropFirst()); 
     liquibase.setShouldRun(liquibaseProperties.isEnabled()); 

     return liquibase; 

    } 

} 

ответ

5

Это потому, что каждый тест содержит одну и ту же базу данных и что жизненный цикл H2 не находится под нашим контролем. Если вы запустите процесс (VM) и потребуете базу данных с именем foo, закройте контекст приложения, запустите новый и потребуйте foo, и вы получите тот же экземпляр.

В предстоящем выпуске 1.4.2 мы добавили свойство для создания уникального имени для базы данных при запуске (см. spring.datasource.generate-unique-name), и это значение будет установлено равным true по умолчанию на 1.5.

В то же время вы можете аннотировать каждый тест @SpringBootTest(properties="spring.datasource.name=xyz"), где xyz отличается для теста, требующего отдельной БД.

+0

Благодарим за быстрый ответ. К сожалению, это изменение не помогло мне. Может быть, из-за того, как я настроил БД? Я обновил оригинальный пост с дополнительной информацией. – TechCrunch

+0

Да, похоже, JHipster полностью игнорирует это свойство, и вы все равно не используете автоконфигурацию. Попробуйте добавить тег jhipster к вашему вопросу, чтобы привлечь больше внимания. –

+0

Я использовал 'spring.datasource.url' вместо имени. Я отлично разбираюсь в том, что в тесте интеграции сейчас. Я хотел бы настроить это в конфигурации базы данных, но у меня есть два источника данных, подключающихся к одной и той же БД, но с разными разрешениями, и трудно обойти случайное значение для имени базы данных для обоих из них. Возможно, я мог бы попробовать статическую переменную в классе конфигурации базы данных. Спасибо за помощь. – TechCrunch

0

Если я все правильно понимаю, Liquibase заботится о статусе базы данных. Для каждого файла, также для тестовых данных, Liquibase создает контрольную сумму в таблице, чтобы проверить, что-то изменилось или нет. Экземпляр h2 все еще жив после @DirtiesContext, поэтому контрольные суммы все еще существуют в базе данных. Liquibase считает, что все правильно, но данные теста могут измениться.

Чтобы заставить Liquibase отказаться от базы данных и воссоздать полностью новую базу данных, вы должны установить свойства в приложении.YML (что один для испытаний):

liquibase: 
    contexts: test 
    drop-first: true 

или в качестве альтернативы вы можете жёстко его:

liquibase.setDropFirst(true); 

Вы можете аннотировать тест с @DirtiesContext, который замедляет испытания, потому что для всего приложения контекст восстанавливается.

Или вы можете создать пользовательский TestExecutionListener, который намного быстрее. Я создал пользовательский TestExecutionListener, который воссоздает базу данных и сохраняет контекст.

класс CleanUpDatabaseTestExecutionListener общественного расширяет AbstractTestExecutionListener {

@Inject 
SpringLiquibase liquibase; 

@Override 
public int getOrder() { 
    return Ordered.HIGHEST_PRECEDENCE; 
} 

@Override 
public void afterTestClass(TestContext testContext) throws Exception { 
    //This is a bit dirty but it works well 
    testContext.getApplicationContext() 
     .getAutowireCapableBeanFactory() 
     .autowireBean(this); 
    liquibase.afterPropertiesSet(); 
} 

, если вы используете TestExecutionListener вам необходимо добавить прослушиватель к тесту с:

@RunWith(SpringJUnit4ClassRunner.class) 
@SpringApplicationConfiguration(classes = Application.class) 
@WebAppConfiguration 
@IntegrationTest 
@TestExecutionListeners(listeners = { 
    DependencyInjectionTestExecutionListener.class, 
    TransactionalTestExecutionListener.class, 
    CleanUpDatabaseTestExecutionListener.class, 
}) 
public class Test { 
    //your tests 
} 

ПРИМЕЧАНИЕ: НЕ ИСПОЛЬЗОВАТЬ @DirtiesContext а TestExecutionListener вместе, это приведет к ошибке.

0

Устранено путем удаления username, url и password параметров.

spring: 
autoconfigure: 
    exclude: org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration 
jackson: 
    serialization: 
    indent_output: true 
datasource: 
    driver-class-name: org.hsqldb.jdbcDriver 
    generate-unique-name: true 
jpa: 
    hibernate: 
    dialect: org.hibernate.dialect.HSQLDialect 
    ddl-auto: validate 
    show-sql: true 
h2: 
    console: 
    enabled: false 

liquibase: 
change-log: classpath:/liquibase/db.changelog-master.xml 
drop-first: true 
contexts: QA 

 Смежные вопросы

  • Нет связанных вопросов^_^