【问题标题】:Is it possible to change the value of a final field in Java for the purposes of testing?是否可以更改 Java 中 final 字段的值以进行测试?
【发布时间】:2014-05-11 14:25:41
【问题描述】:

我想我已经知道这个问题的答案了,但问一下也无妨。

我有一个类来处理对外部服务的请求。该类有一个字段可以帮助它保持在服务速率限制内:
public static final int REQUEST_TIMEFRAME_MILLISECONDS = 600000;
由于此值由服务强制执行,并且不太可能更改,因此我将其标记为 final,以便它不会被覆盖,并且对它们的任何手动更改都会让人停下来思考。

一切正常,直到我开始测试。由于该值设置为 10 分钟,因此它会导致测试运行的时间比我想要的要长得多。出于测试的目的,有什么方法可以让我正常使用该类,但使用较低的值?还是我需要将其重构为可以通过子类更改的protected static int
它是 Android 应用程序的一部分,我正在使用 Junit3 和 Mockito 进行测试,如果这有什么不同的话。

【问题讨论】:

标签: java android mockito final junit3


【解决方案1】:

如果您从属性文件中读取值并限制此变量的范围,那么它也可以满足您的要求。

【讨论】:

  • 听起来可以。你能举一个从属性文件中读取的例子吗?
【解决方案2】:

rpg711above 链接到一种使用反射更改字段的方法,但我建议不要这样做。请记住,Java 允许inline static final fields per JLS 13.4.9:

如果一个字段是一个常量变量(第 4.12.4 节),那么删除关键字 final 或更改其值不会因为导致它们不运行而破坏与预先存在的二进制文件的兼容性,但它们不会看到任何新值除非重新编译,否则该字段的使用。即使用法本身不是编译时常量表达式也是如此(第 15.28 节)

JLS 17.5.3 中也将其作为警告提及,如在linked answer 中。

除此之外,还要记住有人(也许是未来的你)有一天会阅读代码并做出(非常公平的)static final int 值永远不会改变的假设。您可能只是在测试中更改它,但它仍然在更改,下次您必须弄清楚测试失败的原因时,您可能会对这种行为感到惊讶。


虽然您可以放宽修饰符以使其成为非最终的protected static int,但我更喜欢“测试覆盖”或“测试重载”方法,具体取决于您是否喜欢子类或可见的测试方法。 Override-for-testing 看起来像这样:

public class YourComponent {
  protected int getRequestTimeframeMilliseconds() {
    return 600000;
  }
}

public class YourComponentTest {
  private static class YourComponentForTesting extends YourComponent {
    @Override protected int getRequestTimeframeMilliseconds() {
      return 500;
    }
  }

  @Test public void shouldTimeout() { /* ... */ }
}

...实际上非常高效,因为 JVM 可以inline the method。测试重载看起来像这样:

public class YourComponent {
  public static final int DEFAULT_REQUEST_TIMEFRAME_MILLISECONDS = 600000;

  public ReturnObject callService() {
    return callService(DEFAULT_REQUEST_TIMEFRAME_MILLISECONDS);
  }

  /** Package-private for testing. */
  ReturnObject callService(int timeframe) {
    /* Your implementation here. */
  }
}

如果您不介意覆盖,前者可能会很好,但如果您是依赖注入的粉丝,后者特别方便。如果您将被测系统和测试放在同一个包中(即使它们位于不同的源代码树中),两者都将从包私有可见性中受益。请注意,无论哪种方式,您都为 API 提供了一个明显且简单的默认值,但您明确表示有一种方法可以获得不同的行为。测试是组件的消费者,让他们通过 API(而不是通过后门)修改行为可以帮助您定义组件的行为。

【讨论】:

  • 这太棒了。测试的覆盖非常简单,我可以轻松实现它。这正是我需要的,谢谢!
  • 不客气!我忘了提到,如果你愿意帮助封装,你可以使那个可覆盖的方法protected 或包私有。 FWIW,VM 通常对 inlining little methods like that 很好。祝你的项目好运!
猜你喜欢
  • 1970-01-01
  • 2019-09-30
  • 1970-01-01
  • 1970-01-01
  • 2012-02-15
  • 1970-01-01
  • 1970-01-01
  • 2013-12-15
  • 1970-01-01
相关资源
最近更新 更多