【发布时间】:2020-12-06 16:56:35
【问题描述】:
我是 Java 单元测试的新手;我有一个使用 Hibernate 与 MySQL 数据库交互的应用程序。
我有许多使用createQuery() 方法构建的查询,也带有参数,如下所示:
return this.entityManager.createQuery("from MyEntity m where param = :param", MyEntity.class)
.setParameter("param", param)
.getSingleResult();
我想避免模拟对 entityManager 对象的所有后续调用,因为有时我会使用超过 5 个参数进行查询,而且模拟每个调用似乎并不方便。
同样的概念可以应用于Builder objects。
编辑 1
我添加了一个我使用的具体示例(鉴于这不是管理异常的好方法,但不幸的是通常很安静):
public class MyService {
private EntityManager entityManager;
public MyEntity find(String field ) {
try{
return this.entityManager.createQuery("from MyEntity c where c.field = :field ", MyEntity .class)
.setParameter("field ", field )
.getSingleResult();
} catch (NoResultException e) {
return null;
} catch (NonUniqueResultException e) {
logger.error("find", e);
return null;
}
}
}
在这个例子中,考虑到entityManager 上的调用行为,我有不同的分支需要测试。然后我必须模拟那个调用的答案来测试这个方法的所有行。
我发现了什么
我发现如下:
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private EntityManager entityManager;
按预期工作。我可以模拟所有调用链。 但是
警告:常规干净代码很少需要此功能!把它留给遗留代码。模拟一个模拟返回一个模拟,返回一个模拟,(...),返回一些有意义的暗示违反德墨忒耳法则或模拟一个值对象(一个众所周知的反模式)。
- 如果上一点还不够,下一点,后面几行,显然设置了一个很大的限制:
当链中包含的任何返回类型的方法不能被模拟时(例如:是原始类或最终类),此功能将不起作用。这是因为 java 类型系统。
第二点意味着如果我尝试以这种方式模拟方法executeUpdate(),它返回一个int,它会引发异常。
when(entityManager.createQuery(anyString())
.setParameter(eq("param"), anyString())
.executeUpdate())
.thenReturn(1);
这样我就无法测试与entityManager 的交互。
问题
- 我应该如何模拟
entityManager上的调用?在我看来,我必须一个一个地模拟每个方法。 -
Answers.RETURNS_DEEP_STUBS用错了吗?如果没有,我该如何处理第二个例子?
【问题讨论】:
-
我的建议是专门使用集成测试来测试与
EntityManager(或任何其他外部低级API)交互的代码。尝试为单元测试模拟它是浪费时间,而且无论如何只能使用集成测试对查询进行可靠测试。如果您对EntityManagerAPI 有一个抽象(例如Dao对象),该层您可以轻松地模拟其他单元测试 -
反问:为什么要先嘲笑实体经理?为什么不针对数据库进行测试,例如内存中的 h2 还是通过 testcontainers 启动的成熟数据库? “单元测试”中的“单元”不一定是“类”。
-
@crizzis 没有 dao。服务直接访问 entityManager。我将使用代码示例更新问题。请注意,这种模式用于数百个类、数千种方法、许多项目中,因此无法更改:)
-
@Turing85 因为如果我得到正确的单元测试,则不会涉及数据库或外部 api 调用或类似的东西。但我问是因为我必须学习,所以每条建议都是一个好的开始!
-
尽管与数据库无关的想法很好,但在 99% 的情况下,您 - 以一种或另一种方式 - 绑定到特定的 DBMS(例如,索引未在 ANSI-SQL 中定义) 并且抛出的异常(至少是消息)在实现之间会有所不同。在这方面,我建议使用实际使用/将在生产中使用的 DBMS(es)测试持久层。当然,这是以用测试数据填充数据库为代价的。
标签: java hibernate junit mockito junit5