BeforeClass с использованием транзакционных тестов Spring

Я использую классы транзакционных тестов Spring для модульного тестирования моего кода DAO. То, что я хочу сделать, - создать мою базу данных один раз, прежде чем все тесты будут выполнены. У меня есть аннотированный метод @BeforeClass, но он работает до того, как Spring загружает контекст приложения и настраивает jdbcTemplate, поэтому на самом деле у меня на самом деле нет соединения с БД. Есть ли способ запустить мою установку БД один раз после загрузки контекста, но до запуска тестов?

Этот thead задает один и тот же вопрос, но принятое решение похоже просто "не делает этого". Я склонен сказать, что это просто похоже на то, что это невозможно.

Ответ 1

Я бы посоветовал вам сделать каждый из ваших тестов автономным и, следовательно, сделать все ваши настройки с @Before, а не с @BeforeClass.

Если вы хотите придерживаться своего подхода, просто используйте метод @Before и попробуйте простую булевскую проверку, чтобы проверить, была ли эта настройка уже завершена. например

 if(!databaseSetup) {
    ...set up the database
    databaseSetup=true;
    }

Не слишком причудливый, но он будет работать!

См. мой ответ здесь для примера теста транзакции spring с аннотациями с использованием dbunit.

Надеюсь, это поможет!

Ответ 2

мое решение, немного сложное, но мне это нужно для тестовой среды:-) не бойтесь немецких javadocs, имена методов и тела должны быть достаточно, чтобы получить его

FIRST создать аннотацию для отметки класса или метода работы с базой данных (создать таблицы и/или вставки)


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

    /**
     * Location der Schemadatei(en). Die Datei darf nur SQL Statements enthalten.
     * Wird keine Location gesetzt, greift der Defaultwert.
     * @return String
     */
    String[] locationsBefore() default {"input/schemas/before.sql"};

    /**
     * Location der Schemadatei(en). Die Datei darf nur SQL Statements enthalten.
     * Wird keine Location gesetzt, greift der Defaultwert.
     * @return String
     */
    String[] locationsAfter() default {"input/schemas/after.sql"};

    /**
     * Ein SchemaImport findet nur bei passender Umgebungsvariable statt, mit diesem
     * Flag kann dieses Verhalten geändert werden.
     * @return boolean
     */
    boolean override() default false;
}

SECOND создать прослушиватель, который ищет аннотацию, AbstractTestExecutionListener является Spring Framework Class из


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>org.springframework.test</artifactId>
            <version>2.5.6</version>
        </dependency>


public class SchemaImportTestExecutionListener extends AbstractTestExecutionListener
        implements ApplicationContextAware {

    /**
     * Standard LOG Definition.
     */
    private static final Logger LOG = LoggerFactory.getLogger(
            SchemaImportTestExecutionListener.class);
    /**
     * Datasource Name - gemeint ist der Name der Datasource Bean bzw. die ID.
     */
    private static final String DATASOURCE_NAME = "dataSource";
    /**
     * JDBC Template.
     */
    private SimpleJdbcTemplate simpleJdbcTemplate;
    /**
     * Flag um festzustellen ob prepareTestInstance schon gerufen wurde.
     */
    private boolean isAlreadyPrepared = false;

    /**
     * Standard Constructor, laut API von konkreten Implementierungen für
     * TestexecutionListener erwartet, es geht aber auch ohne.
     */
    public SchemaImportTestExecutionListener() {
    }

    /**
     * Für jede Testklasse die mit der {@link SchemaImport} Annotation ausgezeichnet
     * ist, wird ein entsprechender SchemaImport durchgeführt.
     * 

* Der SchemaImport findet pro Klasse exakt einmal statt. Diese Verhalten * entspricht der BeforeClass * Annotation von JUnit. *

* Achtung mit Nutzung von Schemaimport auf Klassenebene ist kein * Rollback möglich, stattdessen SchemaImport auf Methodenebene nutzen. * * @param testContext * @throws java.lang.Exception */ @Override public void prepareTestInstance(TestContext testContext) throws Exception { final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestClass(), SchemaImport.class); if ((annotation != null) && !isAlreadyPrepared && (isPropertyOrOverride(annotation))) { executeSchemaImports(testContext, annotation.locationsBefore(), true); isAlreadyPrepared = true; } } /** * Für jede Testmethode mit {@link SchemaImport} werden die angegebenen * Schema Dateien als SQL ausgeführt. * * @param testContext * @throws java.lang.Exception */ @Override public void beforeTestMethod(TestContext testContext) throws Exception { // nur für Methoden mit passender Annotation Schemaimport durchführen final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestMethod(), SchemaImport.class); if (annotation != null) { executeSchemaImports(testContext, annotation.locationsBefore(), true); } } @Override public void afterTestMethod(TestContext testContext) throws Exception { // nur für Methoden mit passender Annotation Schemaimport durchführen final SchemaImport annotation = AnnotationUtils.findAnnotation(testContext.getTestMethod(), SchemaImport.class); if (annotation != null) { executeSchemaImports(testContext, annotation.locationsAfter(), false); } } /** * Prüfen ob passende Umgebungsvariable gesetzt wurde. Diese kann durch * entsprechendes Setzen des Flags an der Annotation überschrieben werden. * @return */ private boolean isPropertyOrOverride(SchemaImport annotation) { String prop = System.getProperty(TYPEnviroment.KEY_ENV); if (StringUtils.trimToEmpty(prop).equals(TYPEnviroment.EMBEDDED.getEnv())) { LOG.info("Running SchemaImport, Enviroment is set:'" + prop + "'"); return true; } else { if (annotation.override()) { LOG.warn( "Running SchemaImport, although Enviroment is set:'" + prop + "'"); return true; } else { LOG.warn( "Not Running SchemaImport cause neither Environment or SchemaImport.override are set."); return false; } } } /** * Hilfesmethode die eigentlichen SchemaImport kapselt. * * @param testContext * @param locations */ private void executeSchemaImports(TestContext testContext, String[] locations, boolean checkLocations) { // für jede Datei SchemaImport durchführen, korrekte Reihenfolge // ist durch Entwickler zu gewährleisten if (locations.length > 0) { for (String location : locations) { if (StringUtils.trimToNull(location) != null) { if (isResourceExistant(location, checkLocations)) { LOG.info("Executing Schema Location: '" + location + "'"); SimpleJdbcTestUtils.executeSqlScript(getJdbcTemplate( testContext), new ClassPathResource(location), false); } else { LOG.warn( "Schema Location '" + location + "' for SchemaImport not found."); } } else { throw new RuntimeException("SchemaImport with empty Locations in:'" + testContext.getTestClass().getSimpleName() + "'"); } } } } /** * * @param resource * @return */ private boolean isResourceExistant(String resource, boolean checkLocations) { try { new ClassPathResource(resource).getInputStream(); return true; } catch (IOException ex) { if (checkLocations) { throw new RuntimeException(ex); } else { return false; } } } /** * Hilfsmethode um an ein JdbcTemplate heranzukommen. * * @param TestContext * @return SimpleJdbcTemplate */ private SimpleJdbcTemplate getJdbcTemplate(TestContext context) { if (this.simpleJdbcTemplate == null) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(getDataSource( context)); } return this.simpleJdbcTemplate; } /** * Hilfsmethode um an die Datasource heranzukommen. * * @param testContext * @return DataSource */ private DataSource getDataSource(TestContext testContext) { return (DataSource) testContext.getApplicationContext().getBean( DATASOURCE_NAME, DataSource.class); } /** {@inheritDoc} */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { throw new UnsupportedOperationException("Not supported yet."); } }

THIRD добавить слушателя к выполнению теста


@ContextConfiguration(locations = {"classpath*:spring/persistence/*.xml"})
@Transactional
@TestExecutionListeners({
    TransactionalTestExecutionListener.class,
    SchemaImportTestExecutionListener.class})
public abstract class AbstractAvHibernateTests extends AbstractAvTests {

    /**
     * SimpleJdbcTemplate für Subclasses verfügbar.
     */
    @Autowired
    protected SimpleJdbcTemplate simpleJdbcTemplate;
}

в использовании


@SchemaImport(locationsBefore={"schemas/spring-batch/2.0.0/schema-hsqldb.sql"})
public class FooTest extends AbstractAvHibernateTests {
}

важно отметить, что опасаться проблем с потоком при использовании параллельного тестирования testNg, для этого необходимо наличие некоторых "синхронизированных" маркеров для методов getJdbcTemplate/dataSource в слушателе

пс:

код для тестового базового класса:


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:spring/*.xml"})
@TestExecutionListeners({
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    LogDurationTestExecutionListener.class,
    LogMethodNameTestExecutionListener.class})
public abstract class AbstractAvTests implements ApplicationContextAware {

    /**
     * Logger für Subclasses verfügbar.
     */
    protected final Logger LOG = LoggerFactory.getLogger(getClass());
    /**
     * {@link ApplicationContext} für Subclasses verfügbar.
     */
    protected ApplicationContext applicationContext;

    /** {@inheritDoc } */
    @Override
    public final void setApplicationContext(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
}

LogDurationTestExecutionListener и LogMethodNameTestExecutionListener - это пользовательские прослушиватели, не предоставленные spring, но не необходимые для правильной работы schemaImport.

Ответ 3

Я не знаю, какую платформу модульного тестирования вы используете, но для JUnit вы можете создать подкласс класса test AbstractTransactionalJUnit4SpringContextTests, который имеет метод executeSqlScript, который может быть запущен в методе beforeclass или beforemethod. Мое предпочтение заключается в использовании метода BeforeMethod, поскольку это означает, что каждый из моих модульных тестов является автономным, даже если это означает, что мои модульные тесты работают немного медленнее.

Ответ 4

Попробуйте использовать старые методы вместо фантастических аннотаций.

@BeforeClass
    public static void beforeClass() {
        ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
                  [...]
    }