【问题标题】:Mock java.time.format.DateTimeFormatter class模拟 java.time.format.DateTimeFormatter 类
【发布时间】:2023-03-23 06:30:01
【问题描述】:

我正在尝试模拟 DateTimeFormatter 类。我做了以下事情:

@RunWith(PowerMockRunner.class)
@PrepareForTest({DateTimeFormatter.class})
public class UnitTest {

private DateTimeFormatter mockDateFormatter;

private AwesomeClass awesomeClass;

@Before
public void setUp() {
    mockDateFormatter = PowerMockito.mock(DateTimeFormatter.class);
    awesomeClass = new AwesomeClass(mockDateFormatter);
}

@Test
public void shouldToTestSomethingAwesome() {
   // Other test code
    PowerMockito.when(mockDateFormatter.format(any(LocalDate.class)))
                    .thenReturn("20150224");
   // Other test code

}

AwesomeClass 使用它来格式化LocalDateTime.now(ZoneId.of("UTC"));。然后,格式化的字符串进一步用于生成另一个字符串。我需要确保正确生成字符串。所以我需要从格式化程序返回一致的日期或模拟 LocalDateTime.now(..) 静态方法

我做错了什么?

【问题讨论】:

  • 为什么你想模拟DateTimeFormatter,出于兴趣?为什么你不只使用一个真实的?我会警惕过度模拟 - 它会使测试变得更加脆弱(并且难以阅读),而不是简单地使用真实代码或具有大致相同行为的假货。
  • 听起来单元测试对我来说是个模拟......
  • 我传递格式化程序的类使用它来格式化LocalDateTime.now(ZoneId.of("UTC"));。然后,格式化的字符串进一步用于生成另一个字符串。我需要确保正确生成字符串。所以我需要从格式化程序返回一致的日期或模拟LocalDateTime.now(..) 静态方法
  • 确实,您应该使用真正的DateTimeFormatter。您“需要确保正确生成字符串”的论点对我来说听起来并不令人信服。测试应旨在验证正确的行为,并尽可能通过被测类的公共合约;当然,您可以传递正确的输入,然后检查是否产生了正确的输出和/或结果;使用DateTimeFormatter 只是一个实现细节。 (顺便说一句,要使其与 PowerMock 一起使用,您还需要准备被测类;但是,最好还是不要模拟。)
  • 我已经在代码中有@PrepareForTest({DateTimeFormatter.class})。无论如何,我使用了@assylias 建议的方法

标签: java mockito powermock


【解决方案1】:

在 mockito wiki 上:Don't mock types you don't own !

这不是一条硬线,但越过这条线可能会产生影响! (很可能会。)

  1. 想象一下模拟第三方库的代码。在对第三个库进行特定升级后,逻辑可能会发生一些变化,但测试套件会执行得很好,因为它是模拟的。所以后来,认为一切都很好,毕竟构建墙是绿色的,软件已经部署并且...... 繁荣
  2. 这可能表明当前设计与此第三方库的分离不够充分。
  3. 另外一个问题是第三方库可能很复杂,甚至需要大量模拟才能正常工作。这会导致过度指定的测试和复杂的固定装置,这本身就会损害紧凑和可读的目标。或者因为模拟外部系统的复杂性而没有足够覆盖代码的测试。

相反,最常见的方法是围绕外部库/系统创建包装器,但应注意抽象泄漏的风险,即过多的低级 API、概念或异常超出了包装器的边界.为了验证与第三方库的集成,编写集成测试,并使其尽可能紧凑和可读。

您没有控制权的 Mock 类型可以被视为 (mocking) 反模式。虽然DataTimeFormatter 几乎是标准,但不应考虑在即将发布的 JDK 版本中不会有任何行为变化(它已经在 API 的其他部分发生了很多次,只需查看 JDK发行说明)。

我的观点是,如果代码需要模拟我不拥有的类型,那么设计应该尽快改变,这样我、我的同事或该代码的未来维护者就不会陷入这些陷阱。

还有其他博客条目的 wiki 链接描述了他们在尝试模拟他们无法控制的类型时遇到的问题。

【讨论】:

  • 现在是 2018 年,您的链接已失效。这个答案的真正含义是什么?谁知道呢!
  • 要么使用该页面中的一些信息充实您的答案,要么将其更改为问题下的评论,其中相同的质量规则不适用。链接确实会断开,所以有一天你的答案可能只是“不要模拟你不拥有的类型!”,这没什么帮助。
  • 从您的第一条评论中我不明白这一点。但你说得对,我会粘贴一些评论。
  • 啊,我明白了。这将教会我尝试幽默。 (是的,那是我想搞笑...
  • 对我来说,这个建议不要模拟你不拥有的类型,最好将其称为“支持 集成 测试”。根据我的经验,嘲弄经常被滥用和误用。除了模拟 3rd-party 库(例如,模拟值类型或集合/映射接口)之外,还有其他几种方法可以做到这一点。当然,有时您确实需要模拟 3rd 方代码(例如,JSF 的 FacesContext 类),但即使在这些情况下,通常最好在编写集成测试时尽量减少模拟。
【解决方案2】:

模拟LocalDateTime.now() 的另一种方法是将时钟注入您的类并更改(或添加另一个)构造函数,如下所示:

AwesomeClass(DateTimeFormatter fmt, Clock clock) {
  //instead of LocalDateTime now = LocalDateTime.now():
  LocalDateTime now = LocalDateTime.now(clock);
}

然后在你的测试中:

new AwesomeClass(formatter, Clock.fixed(the time you want here));

【讨论】:

  • 如果不清楚,为您的测试添加第二个构造函数(包私有)是完全合理的。例如,您现有的构造函数可以更改为使用参数Clock.systemUTC() 调用包私有构造函数。
猜你喜欢
  • 2019-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-12
  • 1970-01-01
  • 1970-01-01
  • 2016-04-03
相关资源
最近更新 更多