JPA - 如何在单元测试之间截断表
Posted
技术标签:
【中文标题】JPA - 如何在单元测试之间截断表【英文标题】:JPA - How to truncate tables between unit tests 【发布时间】:2011-04-24 22:58:21 【问题描述】:我想在每个测试用例之后清理数据库而不回滚事务。我已经尝试过 DBUnit 的 DatabaseOperation.DELETE_ALL,但如果删除违反了外键约束,它就不起作用。我知道我可以禁用外键检查,但这也会禁用测试检查(我想阻止)。
我正在使用 JUnit 4、JPA 2.0 (Eclipselink) 和 Derby 的内存数据库。有什么想法吗?
谢谢, 西奥
【问题讨论】:
为什么每次测试后都需要清理数据库? 避免副作用。我希望每个测试都彼此隔离运行。 【参考方案1】:最简单的方法可能是使用 nativeQuery jpa 方法。
@After
public void cleanup()
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
em.createNativeQuery("truncate table person").executeUpdate();
em.createNativeQuery("truncate table preferences").executeUpdate();
em.getTransaction().commit();
【讨论】:
'truncate' 是 DDL 语句,不需要开始事务,所有 DDL 都是自动提交的。 我同意,但 createNativeQuery 需要一个事务。否则它会抛出异常。至少在 hibernate 中是这样的。 @gadon:这取决于数据库。至少 PostgreSQL、DB2 和 Informix (部分)支持事务性 DDL。然而,甲骨文没有。【参考方案2】:简单:在每次测试之前,启动一个新事务,在测试之后,将其回滚。这将为您提供与以前相同的数据库。
确保测试不会创建新事务;而是重用现有的。
【讨论】:
是的,但这样一来,您将无法捕获事务提交期间发生的任何错误(例如,如果您测试关系等)。 提交只是关闭事务并使数据库中的数据永久化。如果您有 FK 错误,您会在 flush 会话时得到这些错误。无需提交。 OK,这意味着刷新与提交具有相同的效果,除了关闭事务并且将数据永久保存在数据库中。会试一试。谢谢 为了更清楚:flush()
只是创建在内存中挂起的 SQL 语句。这允许您在内存中对模型进行许多更改,然后一次保留所有更改。假设您更改了用户的姓名和电话号码,然后删除了记录。如果没有这样的缓存,那可能是三个 SQL。使用缓存,它只是一个。【参考方案3】:
我有点困惑,因为 DBUnit 会在每次测试之前将数据库重新初始化为已知状态。
他们还recommend as a best practice 不清理或以其他方式更改测试后的数据。
因此,如果您需要清理以准备数据库以进行下一次测试,我不会打扰。
【讨论】:
实际上,我没有使用 DBUnit。我刚刚尝试了提到的 DELETE_ALL 操作。我正在用 JUnit @Before 方法填充我的数据库。 DBUnit 是否与 JPA 完美契合? 我使用 Unitils,它是 dbunit 周围的说唱歌手。你用你关心的东西创建一个数据集(在 xml 中:没有人是完美的),在你的测试类中添加几个注释,你就完成了。请参阅:unitils.org/tutorial.html#Testing_with_JPA【参考方案4】:是的,事务中测试会让您的生活更轻松,但如果事务是您的事,那么您需要在清理期间实施补偿事务(在@After
中)。这听起来很费力,但如果处理得当,您最终可能会得到一组辅助方法(在测试中),以补偿(清理)在@Before
和测试期间积累的数据(使用 JPA 或直接 JDBC - 任何有意义的)。
例如,如果您在测试期间使用 JPA 并在实体上调用 create 方法,您可以使用(如果您喜欢使用 AOP,或者只是像我们这样的辅助测试方法)跨所有测试的模式来:
-
跟踪测试期间创建的所有实体的 ID
按创建顺序累积它们
在
@After
中以相反的顺序为这些实体重放实体删除
【讨论】:
这种方法听起来不错,但对我的口味来说仍然是太多的工作。即使您跟踪创建的实体,如果您以错误的顺序删除它们(由于参照完整性问题),也会出现问题。 是的,对于较小的测试套件来说太复杂了。为防止 RI 问题,实体被删除,顺序与创建相反。主要好处是,完成此操作后,测试清理变得透明,并且对开发人员的开销为零。测试越多越有意义。另一个好处:所有开发人员都使用相同的框架来创建测试数据 - 否则他们的测试要么无法工作,要么会破坏某些东西。【参考方案5】:我的设置非常相似:它是 Derby(嵌入式)+ OpenJPA 1.2.2 + DBUnit。以下是我为当前任务处理集成测试的方式:在每个 @Before
方法中,我运行 3 个脚本:
-
Drop DB — 删除所有表的 SQL 脚本。
创建 DB — 一个重新创建它们的 SQL 脚本。
用于填充数据的特定于测试的 DB 单元 XML 脚本。
我的数据库只有 12 个表,测试数据集也不是很大——大约 50 条记录。每个脚本运行大约需要 500 毫秒,我在添加或修改表时手动维护它们。
这种方法可能不推荐用于测试大型数据库,对于小型数据库,它甚至不能被认为是好的做法;然而,与@After
方法中回滚事务相比,它有一个重要优势:您实际上可以检测到提交时发生的情况(如持久分离实体或乐观锁异常)。
【讨论】:
【参考方案6】:迟到总比不... 我只是遇到了同样的问题,并想出了一个非常简单的解决方案:
-
在持久性单元配置中将属性“...database.action”设置为值“drop-and-create”
每次测试后关闭实体管理器和实体管理器工厂
persistence.xml
<persistence-unit name="Mapping4" transaction-type="RESOURCE_LOCAL" >
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>...</class>
<class>...</class>
<properties>
...
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create" />
...
</properties>
</persistence-unit>
单元测试:
...
@Before
public void setup()
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
entityManager = factory.createEntityManager();
@After
public void tearDown()
entityManager.clear();
entityManager.close();
factory.close();
...
【讨论】:
【参考方案7】:每次运行后我都会删除数据库文件:
boolean deleted = Files.deleteIfExists(Paths.get("pathToDbFile"));
有点脏,但对我有用。 问候
【讨论】:
【参考方案8】:选项 1: 您可以在截断表之前禁用外键检查,并在截断后再次启用它们。您仍然可以通过这种方式检查测试。
选项 2: H2 数据库在最后一个连接关闭时销毁内存数据库。我猜 Derby DB 支持类似的东西,或者你可以切换到 H2。
另请参阅:我在相关问题中使用 Hibernate 在每次测试之前编写了截断表的代码:https://***.com/a/63747005/471214
【讨论】:
以上是关于JPA - 如何在单元测试之间截断表的主要内容,如果未能解决你的问题,请参考以下文章