模拟列表不为空但元素无法迭代?

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

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

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

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

class UnitTest {
  @Test void sum_empty() {
    final List<Integer> listMock = Mockito.mock(List.class);

    assertEquals(-1L, Sum.sum(listMock));
  }

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

    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 mocking mockito tdd
1个回答
0
投票

问题在于

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

存根

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

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

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

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

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

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

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

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

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

class UnitTest {
  @Test void sum_empty() {
    final List<Integer> list = List.of();
    assertEquals(-1L, Sum.sum(list));
  }

  @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.