BeforeClass с использованием Spring транзакционных тестов - PullRequest
10 голосов
/ 13 июня 2009

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

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

Ответы [ 4 ]

5 голосов
/ 16 июня 2009

мое решение, немного сложное, но оно мне понадобилось для тестовой среды :-) не бойтесь немецких 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 от


        <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.
     * <p>
     * Der SchemaImport findet pro Klasse exakt einmal statt. Diese Verhalten
     * entspricht der <a href="http://junit.sourceforge.net/javadoc/org/junit/BeforeClass.html" rel="noreferrer">BeforeClass</a>
     * Annotation von JUnit.
     * <p>
     * <strong>Achtung mit Nutzung von Schemaimport auf Klassenebene ist kein
     * Rollback möglich, stattdessen SchemaImport auf Methodenebene nutzen.</strong>
     *
     * @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

2 голосов
/ 14 июня 2009

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

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

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

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

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

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

1 голос
/ 13 июня 2009

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

0 голосов
/ 13 июня 2009

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

@BeforeClass
    public static void beforeClass() {
        ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
                  [...]
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...