我正在使用Spring事务测试类对我的DAO代码进行单元测试。我想要做的是在所有测试运行之前创建一次我的数据库。我有一个@BeforeClass注释方法,但是在Spring加载应用程序上下文并配置jdbcTemplate之前运行,因此我当时实际上并没有连接到DB。有没有办法在上下文加载后但在测试开始运行之前运行我的数据库设置?
This thead问同样的问题,但接受的解决方案似乎只是“不要那样做”。我倾向于说这似乎是不可行的。
答案 0 :(得分:5)
我的解决方案,有点复杂,但我需要它用于测试框架:-) 不要害怕德国的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. *
* 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方法
PS:
测试基类的代码:
@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无法正常工作
答案 1 :(得分:2)
我的建议是你应该让你的每个测试都是自主的,因此用@Before而不是@BeforeClass进行所有设置。
如果你想坚持你的方法,只需使用@Before方法并进行简单的布尔检查,看看设置是否已经完成。例如
if(!databaseSetup) {
...set up the database
databaseSetup=true;
}
不太花哨,但它会起作用!
有关使用dbunit注释的示例弹簧事务测试,请参阅我的答案here。
希望这有帮助!
答案 2 :(得分:1)
我不知道你正在使用什么单元测试框架但是对于JUnit,你可以使你的测试类子类AbstractTransactionalJUnit4SpringContextTests具有executeSqlScript方法,这可以在beforeclass或beforemethod方法中运行。我的偏好是使用BeforeMethod,因为这意味着我的每个单元测试都是自治的,即使这意味着我的单元测试运行速度稍慢。
答案 3 :(得分:-4)
尝试使用旧方法而不是花哨的注释。
@BeforeClass
public static void beforeClass() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
[...]
}