使用 try-with-resource 时调用断言关闭

Posted

技术标签:

【中文标题】使用 try-with-resource 时调用断言关闭【英文标题】:Assert close is called when using try-with-resource 【发布时间】:2017-07-19 08:36:18 【问题描述】:

我正在尝试为一些打开数据库连接并对数据库执行一些操作的代码编写单元测试。我想断言连接已正确关闭,即使抛出异常也是如此。

我要测试的代码是这样的:

public void methodToTest(final String aName) 
  final String sqlDeleteStatement = "DELETE FROM " + DB_FULL_TABLE + " WHERE name=?";

  try (final Connection connection = this.dataSource.getConnection();
      final PreparedStatement deleteStatement = connection.prepareStatement(sqlDeleteStatement);) 
    connection.setAutoCommit(true);

    deleteStatement.setString(1, aName);
    deleteStatement.executeUpdate();
  
  catch (final SQLException e) 
    // handle error
  

我目前正在使用 jMock 为数据源和连接对象创建模拟实例,并模拟在 connection.prepareStatement 中引发的异常:

public void testConnectionClosed() throws Exception 
  final Mockery mockery = new Mockery();
  final DataSource dataSource = mockery.mock(DataSource.class);
  final Connection connection = mockery.mock(Connection.class);

  final String exceptionMessage = "intentionally thrown " + UUID.randomUUID();

  mockery.checking(new Expectations() 
      oneOf(dataSource).getConnection();
      will(returnValue(connection));

      oneOf(connection).prepareStatement(with(any(String.class)));
      will(throwException(new SQLException(exceptionMessage)));

      oneOf(connection).close();
    );

  final ClassUnderTest cut = new ClassUnderTest(dataSource);
  cut.methodToTest("someName");

  mockery.assertIsSatisfied();

我面临的问题是,测试是绿色的,并且没有connection.close() 的期望。不出所料,我看到了一个被压制的org.jmock.api.ExcpectationError

Suppressed: unexpected invocation: java.sql.Connection1370903230.close()

但是测试并没有失败,因为在 try-with-resource 语句的隐式 finally 块中引发了错误。

我不想仅仅为了让这个测试有意义而重写代码,但我想确保正确的资源处理,而不需要太多关于非常具体的实现细节的知识,比如 try-with-resource 的使用。

有没有办法用 jMock 实现这一点?


不,我不是在寻求关于图书馆的推荐。所以请不要标记为离题。

【问题讨论】:

【参考方案1】:

这里的关键点:您不一定要验证 try-with-resources 是否按照 Java 语言规范所说的那样工作。如果您找不到适用于 jMock 的此问题的解决方案 - 然后选择“下一个最好的东西”。那就是:为确保连接关闭的“好路径”编写一个测试用例。

意思:你的问题是这个测试抛出了一个异常,这意味着关闭调用对你是“隐藏的”。但是当您编写一个抛出 no 异常的测试时,您应该能够验证是否调用了 close()

当您使用 try-with-resources 时,您可以推断出它也会被调用用于错误路径。当然,这不是很优雅。但这是一个务实的解决方案 - 植根于您使用的是“晦涩”的模拟框架这一事实!

因此,真正的答案是:使用合理的模拟框架。

这在某种程度上是固执己见,但 jmock 对于生产用途来说“不合理”——仅仅是因为“没有人”在使用它。它似乎是一个“几乎死了”的项目。当您转向 jmock site 时,您很容易遇到“死”链接。当您查看他们的 github 存在时,您会发现自 2016 年以来只有少数提交。无论如何,只有少数提交者,而且提交的数量在很长一段时间内非常接近于零。

当您依靠开源工具来支持您的项目/产品时,您希望确保有一个活跃的用户和开发社区。因为当你遇到问题时,你需要答案。当您投资(通过花时间获得使用该工具的技能)时,您希望避免押注于死马。

TL;DR:

要么务实,只测试“好案例”;希望没有人“愚蠢”到足以将 try-with-resources 变成老式的 try/catch 更改为不同的模拟框架(例如,mockito 在 SO 上标记的问题比 jmock 多 20

(不要误会我的意思:jmock 可能是一个“不错”的框架 - 但重要的是 活力。不动(或移动太慢)的东西已死。你不会在技术上投资)

鉴于 jmock 是一个已建立的框架这一事实 - 只需考虑“通过进化取得进步”。含义:获得批准添加另一个模拟框架;并简单地开始将它用于任何。这就是我们从 EasyMock 迁移到 Mockito 的方式;而且效果很好。

【讨论】:

我在内部已经提出了对 jMock 的担忧。我们的测试基地非常大,到处都在使用 jMock。由于它适用于 99.9% 的用例,因此很难争论框架切换并获得批准。但是,我能否使用 mockito 编写类似上述的测试并获得所需的行为? 关于您测试好案例并正确处理错误案例的观点:如果我这样做了,我会在测试中添加很多不一定成立的假设。如果有人更改实现并决定不再使用 try-with-resource 而是关闭 try-block 中的连接,那么好的情况仍然会成功,但在错误情况下连接将保持未关闭状态。那就是测试不会发现所描述的重构中的错误。 当然。但是,如果有人打算故意做愚蠢的事情,那么你能做的就没有那么多了。如果那个人不知道他在做什么,他还不如继续“调整”那个失败的测试,直到它给你“绿色”。是的,用 Mockito 或 EasyMock 编写这个测试用例应该是直截了当的。 @dpr 除此之外 - 查看我关于“摆脱 jmock”的更新。最后一个问题:我能做些什么来让我的答案至少值得投票吗? 52.1k 的声望并且仍然支持每一个支持?干得好。感谢您的回答!【参考方案2】:

所以我在使用 Jmock 和 try-with-resources 以及 CloseableHttpResponse 和 CloseableHttpClient 时遇到了类似的问题。这是由于 Jmock 的内部期望方法(在本例中为“close”)只能通过源调用(请参阅What on earth is "Self-suppression not permitted" and why is Javac generating code which results in this error?)。

您需要在您使用的每个可关闭对象上模拟 close 方法。即在 Connection 和 PreparedStatement 上。

【讨论】:

以上是关于使用 try-with-resource 时调用断言关闭的主要内容,如果未能解决你的问题,请参考以下文章

使用try-with-resource优雅关闭资源

为啥 try-with-resources 不能与字段变量一起使用?

使用 try-with-resources 重写此代码

如果我们使用 try-with-resource 是不是需要关闭资源

在 JDK 9 中更简洁使用 try-with-resources 语句

java try-with-resource语句使用