【问题标题】:Reuse production code in tests : good or bad idea? (Or can I use the same code in test and production)?在测试中重用生产代码:好主意还是坏主意? (或者我可以在测试和生产中使用相同的代码)?
【发布时间】:2021-11-05 14:48:54
【问题描述】:

我们的应用程序中有一段生产代码可以读取类似这样的原始数据库行:

 List<Map<String, Object>> results =
        txnNamedJdbcTemplate.queryForList(
            transactionDbQueries.getProperty(QUERY_FETCH_REPORT_DETAILS).trim(), paramMap);

,然后它会执行大量的字段转换以生成所需结构的对象:

private Report extractReportData(long reportId, List<Map<String, Object>> results) {
    Map<String, Object> reportRow = results.get(0);
    Timestamp completeTs = (Timestamp) reportRow.getOrDefault(RS_PARAM_END_DATETIME, null);
    Timestamp lastOpenedTs =
        (Timestamp) reportRow.getOrDefault(RS_PARAM_LAST_OPENED_DATETIME, null);
    String reportData =
        reportRow.get(RS_PARAM_REPORT_DATA) == null
            ? StringUtils.EMPTY
            : ((PGobject) reportRow.get(RS_PARAM_REPORT_DATA)).getValue();
    Duration executionTime =
        reportRow.containsKey(RS_PARAM_DURATION)
            ? Duration.ofSeconds(Long.parseLong(reportRow.get(RS_PARAM_DURATION).toString()))
            : null;
    String reportRunLevel = (String) reportRow.getOrDefault(RS_PARAM_ACCESS_LEVEL, null);
    boolean reportOpened = (Boolean) reportRow.getOrDefault(RS_PARAM_OPENED_STATUS, Boolean.FALSE);
    String reportCategory = (String) reportRow.getOrDefault(RS_PARAM_REPORT_CATEGORY, null);
    Long scheduledId =
        reportRow.get(RS_PARAM_SCHEDULED_ID) != null
            ? Long.parseLong(reportRow.get(RS_PARAM_SCHEDULED_ID).toString())
            : null;
    return Report.builder()
        .reportId(reportId)
        .reportName((String) reportRow.get(RS_PARAM_REPORT_NAME))
        .reportType((String) reportRow.get(RS_PARAM_REPORT_TYPE))
        .reportCategory(reportCategory)
        .reportStatusDesc(
            ReportStatus.values()[(Integer) reportRow.get(RS_PARAM_STATUS_ID) - 1].getDesc())
        .submittedBy((String) reportRow.get(RS_PARAM_USER_NAME))
        .submittedById((int) reportRow.get(RS_PARAM_USER_ID))
        .submittedTime((Timestamp) reportRow.get(RS_PARAM_SUBMIT_DATETIME))
        .completedTime(completeTs)
        .lastOpeningTime(lastOpenedTs)
        .reportData(reportData)
        .reportRunLevel(reportRunLevel)
        .opened(reportOpened)
        .executionTime(executionTime)
        .scheduledId(scheduledId)
        .build();
  }

我知道这不是最漂亮的代码,但那是遗留系统,而且离题了。

现在,我必须测试该代码以确保我们可以读取相同的对象并验证字段,所以我有以下 3 个场景:

  1. 在测试类中克隆此代码。我已经构建了一个测试实用程序来做到这一点。显然,这导致了相同的不漂亮代码的重复,这并不理想。

  2. 另一种方法是将此代码外包给某个实用程序类,让产品和测试代码都使用它,以避免重复。

  3. 此外,还有一种方法可以更改生产类的访问修饰符并进行测试。

这是一个使用适当数据库实例的集成测试,因此使用模拟是行不通的——我们需要读取实际数据。该示例实际上不是最好的,仅用于说明。主要问题:我们是在测试中重用生产代码还是最好复制它?

我强烈倾向于选项 #1,前提是如果我们在 prod 代码的转换中引入错误 - 我们如何检测它?为此,我认为代码隔离是最好的方法。

请问这背后有什么其他的意见或理由吗?

【问题讨论】:

  • 我忘了说,这是一个使用适当数据库实例的集成测试,所以使用模拟是行不通的——我们需要读取实际数据。
  • 所以,您是说您的选择是重用生产代码@bgore?

标签: java unit-testing testing


【解决方案1】:

如果您要在测试范围内复制代码,如果将来添加了一些错误修复,以及如果您的测试没有提醒您生产代码流中发生的事情怎么办?还要考虑如何维护您的将来重复代码。 通常测试不仅是为了验证您当前的代码。它们还记录和反馈/系统,将通知代码中的任何更改/损坏。

如果您无法为您的作品编写测试用例,那么是时候重构您的代码了。

实际上,拆除现有的工作部分并不总是一个好主意,它可能需要一点时间和深入了解代码库和强大的测试套件的历史。但值得我们动手做一点

我会推荐下面的任何一个,

  1. 重构您的代码,使其与单一职责保持一致,并用测试用例覆盖每个单元(正如您所说,至少您可以将它们移至某个实用程序)。
  2. 集成测试
  3. 使用 powermock 或任何类似工具来测试私有方法(注意:出于安全考虑,某些组织可能不喜欢 powermock 等工具)

【讨论】:

    猜你喜欢
    • 2010-11-09
    • 1970-01-01
    • 2021-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 2017-05-26
    相关资源
    最近更新 更多