如何循环模拟列表?

问题描述 投票:0回答:1

注意:此问题和答案旨在作为规范。

我正在为一个简单的方法编写单元测试。由于单元测试中只应测试一个类,因此其他所有内容都必须进行模拟。该方法接受单个列表;模拟输入列表会导致一些非常奇怪的行为。即使我使用 Mockito 设置元素,列表似乎同时为空和非空。

class Sum {
  static long sum(final List<Integer> list) {
    if (list.isEmpty()) {
      return -1L;
    }

    long sum = 0L;
    for (int i = 0; i < list.size(); ++i) {
      sum += list.get(i);
    }
    return sum;
  }
}

class UnitTest {
  @Test void sum_single() {
    final List<Integer> listMock = Mockito.mock();
    Mockito.when(listMock.get(0)).thenReturn(42);

    assertFalse(listMock.isEmpty());   // list is non-empty
    assertEquals(42, listMock.get(0)); // the element is in the list!
    assertEquals(42, Sum.sum(listMock));
  }
}

测试失败并出现以下错误:

org.opentest4j.AssertionFailedError: expected: <42> but was: <0>

什么给予?不知何故,列表中的元素没有被求和,即使我已将其放在列表中的索引 0 处(我什至在测试我的类之前验证测试中的元素)。此外,正如测试本身所验证的那样,该列表显然是非空的(如果它是空的,该方法将返回 -1)。

为什么这个测试不起作用?它按照书本执行所有操作:仅测试一个类,模拟所有其他类,并为模拟设置一个值。我必须更改什么才能让测试验证列表中的元素是否正确求和?

java unit-testing junit mocking mockito
1个回答
2
投票

问题在于

Mockito.mock
创建了一个没有存根行为的模拟对象,这意味着所有方法都将为其返回类型返回默认值(空集合、数字为 0、布尔值为 false、对象为 null)。

存根

get
调用不会神奇地将项目添加到列表中,并且不会影响
isEmpty()
size()
的返回值。嘲笑的“列表”不是真正的列表。它是一个模拟对象,没有任何行为。一个空壳。它只执行测试中明确告知的操作。

应用第一段中的规则让我们推断:

  • mockList.isEmpty()
    返回
    false
    (布尔值的默认值)
  • mockList.size()
    返回
    0
    (数字类型的默认值)

如果不小心应用 Mockito,您最终会得到僵尸对象,这些对象假装它们正在工作,但它们的行为却不像真正的对象。对于列表来说,它看似非空,但同时大小为 0;这对于列表来说没有意义!

并非所有事情都必须被嘲笑。可以测试(同一单元的)对象之间的交互。一个单元不是一个班级! (但也有可能)。甚至 Mockito 本身也建议不要嘲笑一切

如果一切都被嘲笑,我们真的在测试生产代码吗?不要犹豫不要嘲笑!

在同一页上,他们解释说值对象也不应该被模拟。列表显然是一个值对象(它是一个没有任何有趣行为的数据持有者)。

最后但并非最不重要的一点是,不要模拟你不拥有的类型

通过使用真实的列表实例,测试变得更短、更简单、更正确——它实际上有效并且正在测试你的逻辑:

class UnitTest {
  @Test void sum_single() {
    final List<Integer> list = List.of(42);
    assertEquals(42, Sum.sum(list));
  }
}

TLDR:仅在必要时进行模拟。不使用模拟通常更容易开始,并且仅在确实使您的测试更简单且更易于维护时才引入模拟对象。模拟对于隐藏外部复杂行为或抽象 IO 很有用,但它们不应该用于所有用途。一个“单元”可以不仅仅是一个类或一个方法。

最后一次引用 Mockito wiki:

不要犹豫不要嘲笑!

© www.soinside.com 2019 - 2024. All rights reserved.