【问题标题】:How can I unit test that I received correct PreparedStatement?如何对收到正确的 PreparedStatement 进行单元测试?
【发布时间】:2017-03-28 09:43:57
【问题描述】:

我想将合适的对象传递给 verify 方法,而不仅仅是 any()。

有办法吗?

我不能只是采用和复制 Lambda 方法并将结果传递给验证。这不起作用,因为无法直接测试 Lambda。

我的单元测试显然还没有接近测试任何东西:

    @Test
public void testRunTrigger() {
    campaignTrigger.updateCampaignStatus();

    verify(jdbcTemplate).update(any(PreparedStatementCreator.class));
    assertEquals("UPDATE campaign SET state = 'FINISHED'  WHERE state IN ('PAUSED','CREATED','RUNNING') AND campaign_end < ? ", campaignTrigger.UPDATE_CAMPAIGN_SQL);
}

这是我正在测试的课程:

@Component
@Slf4j
public class CampaignTrigger {
final String UPDATE_CAMPAIGN_SQL = String.format("UPDATE campaign SET state = '%s' " +
                " WHERE state IN (%s) AND campaign_end < ? ", FINISHED,
        Stream.of(PAUSED, CREATED, RUNNING)
                .map(CampaignState::name)
                .collect(Collectors.joining("','", "'", "'")));

@Autowired
private JdbcTemplate jdbcTemplate;

@Scheduled(cron = "${lotto.triggers.campaign}")
@Timed
void updateCampaignStatus() {
    jdbcTemplate.update(con -> {
        PreparedStatement callableStatement = con.prepareStatement(UPDATE_CAMPAIGN_SQL);
        callableStatement.setTimestamp(1,  Timestamp.valueOf(LocalDateTime.now()));
        log.debug("Updating campaigns statuses.");
        return callableStatement;
    });
}

任何建议或理论知识,这不是这样做的方法,我将不胜感激。

【问题讨论】:

  • 您可以检查 con.prepareStatement(UPDATE_CAMPAIGN_SQL)callableStatement.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now())) 是否使用适当的参数调用...
  • 您能否提供更多详细信息,我该怎么做?
  • 那种取决于con是否是一个模拟。同时,再次思考,在调用模拟的JdbcTemplate 时捕获参数,正如@GhostCat 建议的那样,更加清晰和优雅
  • 不,这不是模拟 :( 。是的,这是一个不错的方法,但我得到的只是 CampaignTrigger$$Lambda$6/103536485@1e67a849 对象,似乎无法访问它的内部。
  • 你说得对,没什么可看的,因为它是PreparedStatementCreator 接口的实现。分解职责,updateCampaignStatus 应该调用jdbcTemplate.update,具体实现为PreparedStatementCreator。如果您有 2 个不同的类,您可以单独检查您的 PreparedStatementCreator 是否正在生成所需的 PreparedStatement 以及该特定创建者是否调用了 jdbcTemplate。既然都是内联,除了我最初的建议之外,我真的看不出你能做什么(我知道,我改变了两次主意)

标签: java unit-testing junit mocking mockito


【解决方案1】:

您不应该模拟您无法控制的代码。仅模拟您进行测试的代码,因为在模拟时您假设您知道(即您定义)模拟类的工作方式。

在这里,你不知道jdbcTemplate 是如何工作的,也不知道用一些 lambda 调用它是否真的如你所想。

使用您无法控制的代码测试您的代码是集成测试的重点。 IE。您应该将CampaignTrigger 与真实数据库(或内存中的数据库)一起测试,而不是模拟jdbcTemplate

【讨论】:

  • 我同意模拟外部系统行为,但我认为在您的单元测试中使用模拟来检查与该系统的交互是否符合您的期望并没有什么坏处,例如verify(mockConnection).prepareStatement(expectedSQL)。这允许在提交之前运行一些单元测试(应该很快),除了偶尔执行一次的集成测试(通常很慢)。
  • 如果可能的话,我也非常感谢您提供有关最新 cmets 的意见。
  • @Morfic 问题是关于这种单元测试的价值。它比集成测试失败得更快吗?当然。但是,如果某些超出我们控制范围的代码发生更改,它会失败吗?不太可能。所以我们得到的只是维护额外测试的痛苦,因为每当您更改集成测试时,您都需要更改单元测试 - 您有时需要花费几个小时,以便在每次构建时节省几秒钟。集成测试应该很快,它们不需要应用程序启动。如果它们很慢,那么在集成测试之上添加单元测试听起来不是一个好的解决方案。
  • 我可以向您推荐 Steve Freeman 和 Nat Pryce 撰写的“Growing Object Oriented Software Guided by Tests”以供进一步阅读。通常,一个类应该有一个单元测试或一个集成测试,而不是两者。否则,我们也有不同类型的高级黑盒验收测试,它也应该涵盖我们代码中的大多数快乐路径。
  • 好吧,我的意思是问题下方的最新 cmets,抱歉没有具体说明。并感谢您的建议,我会看看它。
【解决方案2】:

您可以通过捕获用于该调用的对象来试试运气,请参阅here。这允许编写这样的代码:

ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());

让您完全访问传递给您的方法调用的对象!请注意,mockito 最近引入了一个 @Captor 注释,使事情变得更容易使用。

编辑;鉴于@Morfic 的cmets:他所说的绝对合理。

这个答案给出了“立即”的提示,你可以如何解决这个特定的问题。

Beyond:合理的方法总是总是将“待测单元”切片......尽可能小!

您的类/方法应该只负责一项职责;然后你确保可以用最简单的方法测试实现。

所以:如果问题是:“我应该使用参数捕获器还是应该更好地修改我的生产代码” - 然后修改你的生产代码。

【讨论】:

  • 既然你是其中的一员,而且我一直在引用你的答案,如果你有时间,你会加入 cmets 部分吗? :-)
  • @GhostCat 感谢您的回答,我试过了,但没有运气。虽然,我从未使用过 ArgumentCaptor,但很高兴看到它在运行。是的,我从其他 cmets/answers 中得到了一点,我需要对代码进行一些修改。下次我会记住的。
  • 学点东西总是好的。这对这里的所有人来说都是如此;-)
猜你喜欢
  • 2010-09-23
  • 2023-03-20
  • 2014-01-30
  • 2011-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-10
  • 2014-03-22
相关资源
最近更新 更多