基于 JPA 的 JUnit 测试最佳实践

Posted

技术标签:

【中文标题】基于 JPA 的 JUnit 测试最佳实践【英文标题】:JPA-based JUnit Test Best Practices 【发布时间】:2010-11-17 02:41:39 【问题描述】:

这是一个有点奇怪的问题,但它已经困扰了我几个月了。我已经使用 Wicket + Hibernate(使用 Maven 构建)构建了一个基于 JPA 的 Web 应用程序,并且想直接测试 DAO 层。我创建了一个用于测试的特定 src/test/resources/META-INF/persistence.xml 文件,但一直与 WTP 等发生冲突。为了解决这些问题,我创建了一个单独的测试项目,单元测试在其中进行。有没有更好的方法来管理 JPA 项目的单元测试,而无需在持久性文件之间进行决斗?

附录:其他测试框架(例如,TestNG)会让这更容易吗?

【问题讨论】:

你提到的这种类型的测试不是单元测试。我认为这是类型集成测试。当您编写单元测试时,您会测试一个模拟所有依赖项的类。所以在单元测试中使用真实的数据库(甚至是内存数据库)是无效的。 它既不是完整的集成测试。这是有效的!它只是不是单元测试。 【参考方案1】:

您可能想试试mockito。测试如下:

您使用 mockito 来“实现”EntityManager。而不是真正的代码,你使用 mockito 的方法说“如果应用程序调用getReference(),则返回这个对象”。在后台,mockito 将创建一个代理实例,该实例拦截 Java 方法调用并返回您指定的值。调用其他方法将返回null

模拟createQuery() 之类的东西的工作方式相同,但您首先需要创建Query 的模型,然后使用与getReference() 相同的方法(返回查询模型)。

由于您不使用真正的 EM,因此您不需要真正的 persistence.xml

如果您可以设置一些属性来更改 persistence.xml 文件的名称,则更简单的解决方案是,但我认为这是不可能的。

其他一些可能有帮助的链接:

How to configure JPA for testing in Maven Suggest a JPA Unit test framework

【讨论】:

我已经研究过使用 Mock 对象(已经为基于 LDAP 的测试做过),这当然是一种选择。在这种特定情况下,我想实际查询数据库以验证端到端的事情,而不仅仅是确保我的 DAO 返回信息。 在这种情况下,第一个链接中有一个解决方案:您可以在 persistence.xml 中指定多个“持久性单元”,并在单元测试中选择不同的。【参考方案2】:

我们在生产和测试运行时使用双重 persistence.xml 文件,但这只是与类路径相关的问题(我们使用 Eclipse,但不严重依赖 WTP 插件)。两者的唯一区别是生产版本不包含实体定义。

我们不使用模拟框架来测试 JPA,因为这不会为我们的测试增加任何价值。测试确实使用与 PostgreSQL 数据库对话的 JPA 运行真实数据访问。

我们的测试方法基于持久层的 Spring 测试框架:事务内测试。我们的应用程序是基于 Spring 的,但这种方法同样适用于希望利用 Spring 测试类的任意应用程序。本质是每个测试都在一个从不提交的事务中运行,最后(在拆卸中)它会自动回滚。这以非常好的不显眼和透明的方式解决了数据污染和测试依赖的问题。

Spring 测试框架很灵活,可以进行多事务测试,但这些是不超过 10% 测试的特殊情况。

我们仍然使用legacy support for JUnit 3.8,但用于 JUnit 4 的新 Spring TestContext Framework 看起来非常有吸引力。

为了设置交易中测试数据,我们使用构建业务实体的内部实用程序类。由于它在所有测试之间共享,因此维护和支持它的开销大大超过了使用标准和可靠的方法来设置测试数据的好处。

Spring DI 有助于使测试变得简洁和自我描述,但这不是一个关键特性。

【讨论】:

我一直在使用 JUnit 4.x(我相信最后是 4.6)和 Spring 测试扩展。它们确实有助于设置我的 JPA 环境,但我仍然遇到问题,因为我的生产 persistence.xml 引用了 WEB-INF/lib/common-code.jar,它不能很好地用于测试。 “我们不使用模拟框架来测试 JPA,因为这不会为我们的测试增加任何价值。”。我为它说实话而鼓掌。【参考方案3】:

使用 Spring 和 Spring 的单元测试是最好的方法。使用spring,你不需要两个persistence.xml,因为你的persistence.xml里面什么都没有,一切都由spring指定(我们在persistence.xml中指定的是持久性单元名称),因此你可以更改数据库配置等与弹簧。

正如 topchef 所指出的,spring 的基于事务的单元测试非常棒。

【讨论】:

如何在 Spring 中指定要加载哪些类以及从哪些 jar 中挖掘代码?我似乎错过了一些重要的事情。 我使用 OpenJPA,它需要在运行时启用 -javaagent 并使用 persistence.xml。我应该如何告诉 OpenJPA 代理在 spring 配置中提到的类中搜索,而不是在 persistence.xml 中? 嗯...我认为这个答案可能有点过时了。您确实需要在您的 persistence.xml 中指定您的持久类列表 @AskarKalykov - 我建议您宁愿使用编译时检测而不是在运行时使用它 - 正如我所提到的 - 有必要在 persitence.xml 中包含持久类的列表 老实说,我们在使用 openjpa+spring+junit 时还有其他一些注意事项,因此我们决定迁移到 hibernate。到目前为止,jpa 一切正常。【参考方案4】:

正如这里提到的:http://www.devx.com/java/Article/36785/1954, 您可以从项目的.settings/org.eclipse.wst.common.component 中删除以下行,以避免使用 Web 应用程序部署测试资源。

<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>

【讨论】:

【参考方案5】:

你可以:

    拥有多个持久性单元 有几个persistence.xml并在测试时复制它们,稍后恢复它们 在测试中设置您自己的属性,并使用 mockito 返回您的自定义实体管理器工厂 使用弹簧:https://www.baeldung.com/spring-testing-separate-data-source

前两个选项是所有建议问题中讨论最多的选项,也是迄今为止我最不喜欢的选项。

解决方案 3. 如下所示:

private EntityManager entityManager;

private static EntityManagerFactory entityManagerFactory;

@BeforeClass
public static void mainTestInitClass() 
    Properties pros = new Properties();
    // Override production properties
    pros.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
    pros.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
    pros.setProperty("hibernate.connection.username", "sa");
    pros.setProperty("hibernate.connection.url", "jdbc:h2:mem:some_test_db;DB_CLOSE_DELAY=-1;MVCC=TRUE;DATABASE_TO_UPPER=false");
    pros.setProperty("hibernate.hbm2ddl.auto", "create");

    entityManagerFactory = Persistence.createEntityManagerFactory("your_unit", pros);


@Before
public void mainTestORMSetUp() throws Exception 
    this.entityManager = entityManagerFactory.createEntityManager();

现在您有一个可用于每个测试的实体管理器。使用 mockito 在需要的地方注入它。

解决方案 4:使用 Spring Data+Spring Boot 设置 JPA,因此您不再需要实体工厂,您只需使用两个不同的 application.properties(一个用于 main,一个用于测试)然后您使用您定义的 Spring 实体存储库。或者,您可以使用不同的弹簧轮廓(一个用于测试,另一个用于生产),最终允许您执行相同的操作。这个解决方案是我使用的。查看上面的网址了解更多详情。

【讨论】:

以上是关于基于 JPA 的 JUnit 测试最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

JPA 最佳实践? [关闭]

Spring Boot 最佳实践Spring Data JPA 操作 MySQL 8

将唯一违规异常传播到 UI 的最佳实践

Spring数据国际化最佳实践

maven+SSM+shiro+junit+jetty+log4j环境配置的最佳实践

从一个实例详解敏捷测试的最佳实践