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 - 如何在单元测试之间截断表的主要内容,如果未能解决你的问题,请参考以下文章

使用 JPA2 时如何对 EJB 进行单元测试?

使用 JPA2 时如何对 EJB 进行单元测试?

数据库单元测试框架?

Spring Boot中JPA Repository的Save方法的单元测试

Spring 单元测试 JPA 存储库

对 Spring Data JPA @Query 进行单元测试是不可能的,真的吗?