我正在使用Spring 3.1.1.RELEASE,Hibernate 4.1.0.Final,JPA 2,JUnit 4.8.1和HSQL 2.2.7。我想在我的服务方法上运行一些JUnit测试,并且在每次测试之后,我希望回滚写入内存数据库的任何数据。但是,我不希望将整个测试视为事务。例如,在这个测试中
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest
{
…
@Autowired
private ContractService m_contractService;
@Test
public void testUpdateContract()
{
// Add the contract
m_contractService.save(m_contract);
Assert.assertNotNull(m_contract.getId());
// Update the activation date by 6 months.
final Calendar activationDate = Calendar.getInstance();
activationDate.setTime(activationDate.getTime());
activationDate.add(Calendar.MONTH, 6);
m_contract.setActivationDate(activationDate.getTime());
m_contractService.save(m_contract);
final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
Assert.assertEquals(foundContracts.get(0), m_contract);
} // testUpdateContract
对服务有三次调用(“m_contractService.save”,“m_contractService.save”和“m_contractService.findContractByOppId”),每次调用都被视为一个事务,我想要它。但我不知道在每次单元测试后如何将内存数据库重置为原始状态。
如果我需要提供其他信息,请与我们联系。
答案 0 :(得分:18)
由于您正在使用hibernate,因此您可以使用属性hibernate.hbm2ddl.auto每次启动时创建数据库。您还需要在每次测试后强制重新加载spring上下文。您可以使用@DirtiesContext注释执行此操作。
这可能会给您的测试增加额外的开销,因此另一个解决方案就是手动删除每个表中的数据。
答案 1 :(得分:7)
@DirtiesContext对我来说没有解决方案,因为整个应用程序上下文被破坏,必须在每次测试后创建 - &gt;花了很长时间。
@Before对我来说也不是一个好的解决方案,因为我必须在每个IntegrationTest中创建@Before
所以我决定创建一个TestExecutionListener,它在每次测试后重新创建数据库。 (使用liquibase,但它也适用于flyway和普通的sql)
public class CleanupDatabaseTestExecutionListener
extends AbstractTestExecutionListener {
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase(testContext);
alreadyCleared = true;
} else {
alreadyCleared = true;
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase(testContext);
}
private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
ApplicationContext app = testContext.getApplicationContext();
SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
springLiquibase.setDropFirst(true);
springLiquibase.afterPropertiesSet(); //The database get recreated here
}
}
要使用TestExecutionListenere,我创建了一个自定义测试注释
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OurderApp.class)
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {CleanupDatabaseTestExecutionListener.class}
)
public @interface OurderTest {
}
最后但并非最不重要的是我现在可以创建测试,我可以确定数据库处于干净模式。
@RunWith(SpringRunner.class)
@OurderTest
public class ProductSaveServiceIntTest {
}
编辑:我改进了我的解决方案。我遇到的问题是,有时一个测试方法会破坏我的数据库,以便测试类中所有即将进行的测试。所以我创建了注释
package com.ourder.e2e.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClearContext {
}
并将其添加到CleanupDatabaseTestExectionListener。
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
cleanupDatabase(testContext);
}
super.afterTestMethod(testContext);
}
在这两个片段的帮助下,我现在可以创建这样的测试:
@Test
@ClearContext
public void testWhichDirtiesDatabase() {}
答案 2 :(得分:2)
您可以在@Transactional
的Junit班级使用org.springframework.transaction.annotation.Transactional
注释。
例如:
package org.test
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class ArmyTest{
}
答案 3 :(得分:1)
创建一个@Before
方法,从中删除数据库中的所有数据。您正在使用Hibernate,因此您可以使用HQL:delete from Contract
。
答案 4 :(得分:1)
从 JUnit 5 开始,您还可以创建自定义扩展并从 Spring 上下文访问数据源,就像这样(使用 Kotlin):
class DatabaseCleanerExtension : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
ds.connection.use { connection ->
connection.prepareStatement("DELETE FROM my_table").execute()
}
}
}
然后您可以按如下方式注册扩展程序:
@SpringBootTest
@ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
现在,在每次测试后都会执行回调,您可以轻松地注释这适用于的任何测试类。
答案 5 :(得分:0)
对于每个测试,我都使用随机内存数据库解决了相同的问题:
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})
答案 6 :(得分:0)
如果您使用flyway进行迁移,我将使用以下模式:
public class RepositoryFactory
{
public static TRepository GetRepositoryInstance<T, TRepository>()
where TRepository : IRepository<T>, new()
where T : class, IEntity
{
return new TRepository();
}
}
result=[]
for (i, r1), (j, r2) in itertools.combinations(df.iterrows(), 2):
if r1['SERVICE CODE'] != r2['SERVICE CODE']:
result.append('{}_{}'.format(r1['PORT CODE'], r2['PORT CODE']))
dff=pd.DataFrame(result)
使您可以使import os
import numpy as np
from concurrent.futures import ProcessPoolExecutor
def f(x, y):
print(os.getpid(), x, y, x + y)
return x + y
if __name__ == '__main__':
xs = np.linspace(5, 7, 3).astype(int)
ys = np.linspace(1, 3, 3).astype(int)
with ProcessPoolExecutor() as executor:
results = executor.map(f, xs, ys)
for res in results:
print(res)
为非静态,因此每个测试类只能迁移一次。如果要为每个测试重置它,请删除类注释,并将@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
@Autowired
Flyway flyway;
@BeforeAll
public void cleanUp(){
flyway.clean();
flyway.migrate();
}
}
更改为@TestInstance
。