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");
[...]
}