基于JPA的JUnit测试最佳实践

30

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

补充说明:其他测试框架(例如TestNG)会使这变得更容易吗?


你提到的这种测试类型不是单元测试,我认为它属于集成测试。当你编写单元测试时,你会使用伪造的依赖项来测试一个类。因此,在单元测试中使用真实数据库(即使是内存数据库)是无效的。 Translated text: 你提到的这种测试类型不是单元测试,我认为它属于集成测试。当你编写单元测试时,你会使用伪造的依赖项来测试一个类。因此,在单元测试中使用真实数据库(即使是内存数据库)是无效的。 - Mostafa Rezaei
这不是完整的集成测试,但它是有效的!它只不过不是单元测试。 - Gilberto
5个回答

15
您可能想尝试Mockito。 测试的工作原理如下:
您使用Mockito来“实现” EntityManager 。 您使用mockito方法,而不是真正的代码,来指定“如果应用程序调用 getReference(),则返回此对象”。 在后台,mockito将创建一个代理实例,拦截Java方法调用并返回您指定的值。 对其他方法的调用将返回 null
createQuery()这样的 mocking 也是同样的方式,但您首先需要创建一个 Query 的 mockup ,然后使用与 getReference()相同的方法(返回查询mockup)。
由于您没有使用真正的EM,因此您不需要真正的 persistence.xml
更简单的解决方案是,如果可以设置一些属性来更改 persistence.xml 文件的名称,但我认为这是不可能的。
以下是一些可能有所帮助的其他链接:

我已经研究过使用模拟对象(在基于LDAP的测试中已经做过了),这肯定是一个选项。在这种特定情况下,我想实际查询数据库以验证整个流程,而不仅仅是确保我的DAO返回信息。 - mlaccetti
2
在这种情况下,第一个链接中有一个解决方案:您可以在persistence.xml中指定多个“持久化单元”,并在单元测试中选择不同的单元。 - Aaron Digulla

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

我们不使用模拟框架来测试JPA,因为这对我们的测试没有任何价值。测试确实会使用与PostgreSQL数据库交互的JPA进行真实的数据访问。
我们的测试方法基于Spring测试框架的持久层:事务内测试。我们的应用程序基于Spring,但这种方法同样适用于想要利用Spring测试类的任意应用程序。其本质是每个测试都在单个事务中运行,该事务永远不提交,并且在结束时(在tearDown中)会自动回滚。这以非常好的方式解决了数据污染和测试依赖性问题,而且非常不显眼和透明。
Spring测试框架很灵活,可以允许多事务测试,但这些是特殊情况,不超过10%的测试。

我们仍然使用JUnit 3.8 的遗留支持,但是 JUnit 4 的新 Spring TestContext 框架 看起来非常有吸引力。

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

Spring DI 帮助使测试简洁且自描述,但这不是一个关键特性。


我一直在使用JUnit 4.x(据我最后统计,是4.6版本),以及Spring测试扩展。它们在设置我的JPA环境方面非常有帮助,但因为我的生产persistence.xml引用了WEB-INF/lib/common-code.jar,这在测试中并不起作用,所以仍然存在问题。 - mlaccetti
我们不使用模拟框架来测试JPA,因为这对我们的测试没有任何价值。我为说出实话而鼓掌。 - magallanes

4
使用Spring和Spring的单元测试是最好的选择。使用Spring,您不需要两个persistence.xml文件,因为您的persistence.xml文件中没有任何内容,一切都由Spring指定(我们在persistence.xml中只指定持久化单元名称),因此您可以使用Spring更改数据库配置等内容。
正如topchef所指出的那样,Spring的基于事务的单元测试非常棒。

你如何在Spring中指定要加载哪些类和从哪些jar中提取代码?我似乎错过了一些重要的东西。 - mlaccetti
我使用OpenJPA,它需要在运行时启用-javaagent,并使用persistence.xml。我应该如何告诉OpenJPA代理在Spring配置中搜索类,而不是在persistence.xml中搜索? - Askar Kalykov
嗯...我认为那个答案可能有点过时了。你确实需要在你的persistence.xml文件中指定你的持久化类列表。 - Michael Wiles
@AskarKalykov - 我建议您使用编译时插装而不是在运行时进行 - 正如我所提到的 - 必须在您的persitence.xml中包含持久类列表。 - Michael Wiles
说实话,我们在使用openjpa+spring+junit时遇到了一些问题,所以我们决定转向hibernate。到目前为止,使用jpa一切正常。 - Askar Kalykov

0
如此提到: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"/>

0

您可以:

  1. 拥有多个持久化单元
  2. 在测试中复制多个 persistence.xml 文件,并在需要时进行还原
  3. 在测试中设置自己的属性,并使用 Mockito 返回自定义的实体管理器工厂
  4. 使用 Spring: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,这样你就不需要Entity Factory了,你只需使用两个不同的application.properties文件(一个用于主要应用程序,另一个用于测试),然后使用你定义的Spring Entity Repository。或者你可以使用不同的spring配置文件(一个用于测试,另一个用于生产),这将允许你做同样的事情。这是我使用的解决方案。查看上面的URL获取更多详细信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接