【问题标题】:Mockito Spy'ing on the object being unit testedMockito 监视正在单元测试的对象
【发布时间】:2013-10-16 03:52:59
【问题描述】:

监视正在单元测试的对象是否是代码异味?例如,假设我有一个 LineCounter 类,其工作是简单地计算字符串中的行数。 --

class LineCounter {
    public int getNumLines(String string) {
        String metadata = getStringMetadata(string);

        // count lines in file
        return numLines;
    }

    /** Expensive operation */
    protected String getStringMetadata(String string) {
        // do stuff with string
    }
}

现在我想为此编写一个 JUnit 4 测试来测试 getNumLines 方法,同时模拟昂贵的 getStringMetadata 调用。我决定使用 Mockito 的间谍机制让 getStringMetadata 返回一个虚拟值。

class LineCounterTests {
    @Test public void testGetNumLines() {
        LineCounter lineCounterSpy = Mockito.spy(new LineCounter());

        // Mock out expensive call to return dummy value.            
        Mockito.when(lineCounterSpy.getStringMetadata(Mockito.anyString()).thenReturn("foo");

        assertEquals(2, lineCounterSpy.getNumLines("hello\nworld");
    }
}

这是合理的做法吗?我觉得测试一个 Spy 对象而不是实际的类很奇怪,但我真的想不出反对它的理由。

【问题讨论】:

  • 这可能是一个测试驱动改进代码的案例。看起来获取字符串元数据的工作应该被提取到另一个类,然后由LineCounter 委托给另一个类。此时,您已经创建了一个“接缝”,并且可以以更传统(且不那么臭)的方式模拟这种依赖关系
  • @millhouse 是的,完全同意这种方式确实是正确的方式。不过,我想知道,在我的示例中,被测试的对象是间谍,有什么本质上的错误吗?

标签: java unit-testing junit mockito


【解决方案1】:

我将分两部分回答这个问题。首先,是的,模拟或监视被测类是代码气味。这并不意味着它不能正确完成,而是它容易发生风险,应尽可能避免。

WRT 你的具体例子,我会看到如何正确使用间谍,但这将基于你在其他地方已经完全单元测试getStringMetadata 的断言。这就引出了一个问题,如果您在其他地方对getStringMetadata 进行了全面的单元测试,那么您必须知道如何对其进行测试,因此为什么不在没有间谍的情况下测试getNumLines

话虽如此,millhouse 提出了一个很好的观点,但无论哪种方式,您都必须在某处对昂贵的代码进行单元测试。他的建议有助于隔离昂贵的代码并确保您只需测试/练习一次。

【讨论】:

  • 假设我在其他地方测试过getStringMetadata。我仍然想测试getNumLines,而不需要它执行对getStringMetadata 的昂贵调用,因为我已经知道该方法的行为符合预期。 @millhouse 的建议是有效的,我确实喜欢将 getStringMetadata 拆分为另一个类的方法,但我最初的问题仍然存在。我的例子有什么错误。或者问这个问题的更好方法是,为什么将getStringMetadata 提取到另一个类并在测试LineCounter 时模拟该依赖项是一种更好的方法?
  • 你还提到监视被测试的类是有风险的。你能详细说明一下吗?
  • 你问它是否有“代码气味”。我建议这样做。一旦有人看到它,他们就会想“等等,这是怎么回事”。这并不意味着它不能正确完成,只是一般来说应该避免它,这样会引起审稿人的注意。
  • 这是有风险的,因为您可能不是在测试被测类,而是在测试您的模拟/间谍。即使您第一次做对了,其他人也可能在您身后对类或测试进行更改,从而无意中导致测试在其打算测试生产代码的地方执行间谍代码。
【解决方案2】:

在这种情况下,存根由被测方法调用的方法是完全合法的。这甚至是我能想到的单独测试它的唯一方法。您只是不想为了测试而将单个方法提取到它自己的类中。

但请注意存根方法的副作用。存根返回值可能还不够,如果存根方法有副作用,那么您也必须存根副作用。在副作用非常复杂的某些情况下,它甚至可能是反对它的理由,但这很可能表明在测试类本身的实现中存在代码异味。

为了回答您的问题,我发现支持它的原因很容易,但反对它的原因却很难找到。这是我每天都在使用的技术,它帮助我将我的实现拆分为小方法,这些方法在完全隔离的情况下单独测试,我还没有看到它有任何限制。

【讨论】:

  • 我不同意。当您在被测方法调用的被测类中存根一个方法时,这意味着您正在执行白盒测试而不是黑盒测试。您测试的是被测类的实现,而不是它的接口。这样做的缺点是重构被测类也可能需要更改测试。
  • @olenz 但不是所有的 Mockito(或任何模拟框架)都测试白盒测试吗?话虽如此,我同意你的观点,如果可以的话,你应该使用黑盒测试。
猜你喜欢
  • 2017-05-29
  • 1970-01-01
  • 2020-05-03
  • 1970-01-01
  • 1970-01-01
  • 2017-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多