【问题标题】:Mockito Mocking a return value and verify itMockito 模拟返回值并验证它
【发布时间】:2014-04-01 16:44:14
【问题描述】:

我有这个类,想创建一个mock来返回并验证返回值“50”:
QAService.java:

@Path("/QAService")
public class QAService {
@GET()
//@Path("/")
@Produces("text/plain")
public String getServiceInfo() {
    return "50";
}

我对 mock 定义的理解是,我可以创建一个实现类的假对象和尚未开发的 mock 函数,以便我可以测试接口。

我仍然在创建这个测试来测试没有接口的模拟。如何验证?:
QAServiceTest.java:

public class QAServiceTest {

    @Test
    public void getserviceTest () {

    QAService qasmock = mock(QAService.class);
    when(qasmock.getServiceInfo()).thenReturn("50");
    String expected = "50";
    assertEquals(expected, qasmock.getServiceInfo());
    }
}

【问题讨论】:

  • 编译错误说明了什么?
  • 您的QAServiceTest 类是否有该代码所在的方法?
  • " 它没有编译"
  • 等等,如果你有这个类并且你正在尝试测试返回值,你为什么要模拟它?此外,您的 QAServiceTest 代码不在方法内,这可能是它无法编译的原因。
  • 一个类应该在方法中有它的代码。不直接在课堂上。

标签: java unit-testing mocking mockito


【解决方案1】:

Junit 只会运行带有@Test 注释的方法,所以添加它

@Test
public void getserviceTest () {
    QAService qasmock = mock(QAService.class);
    when(qasmock.getServiceInfo()).thenReturn("50");
    String expected = "50";
    assertEquals(expected, qasmock.getServiceInfo());
}

另外,您应该verify() 您的模拟期望确实发生了。

verify(qasmock, times(1)).getServiceInfo();

请注意,您似乎想测试QAService,但您实际上并没有在此处执行此操作。你只是在测试一个模拟。那不是一回事。

自己创建QAService对象并使用它。

【讨论】:

  • 但它仍然是一个模拟吗?我似乎无法明确区分模拟和单元测试。我是在嘲笑这段代码中的QAService.class 吗?建议我如何尝试对QAService 进行一些更改以查看它是否被嘲笑?如果它被嘲笑,那么我对QAService.class 所做的一切都不重要,对吧?
  • @JustinBieber 什么是模拟?如果您想测试QAService 的实现,为什么要模拟它?您对不需要实际实现的依赖项(您的类使用的类)使用模拟。
  • @JustinBieber:如果你删除了一个方法,你打算如何编译一个使用它的测试类。你不明白什么是模拟。假设你有一个炸弹和一个雷管。雷管叫炸弹。你想测试雷管。不是在每次测试时引爆建筑物,而是制造一个假炸弹,并用这个假炸弹测试雷管。模拟炸弹仍然需要具有与真实炸弹相同的方法。这些方法只是做其他事情。你模拟炸弹来测试雷管。您不会模拟雷管来测试雷管。否则,你测试的是 mockito,而不是你的代码。
  • 我认为这不是 QAService 类的正确单元测试。您实际上测试了模拟的 QAService 类,而不是原始类。这是不正确的使用Mock! Mockito 应该用于模拟类内部的依赖关系,因为我们的议程是测试类内部的一些功能而不是依赖关系。明天如果实际实现发生变化并且 getServiceInfo() 返回 30 而不是 50,您的单元测试仍然会通过(返回 50),尽管它应该失败。
  • @SotiriosDelimanolis,这是给问题的作者的,而不是给你的。很抱歉错过提及这一点。我现在仔细阅读了你的回答,并同意你也提到了我想在这里强调的一点。谢谢..
【解决方案2】:

让我们先搞清楚几个定义:

  • 单元测试是您编写的一小段代码,用于确保您开发的系统运行正常。单元测试通常在JUnit 之类的框架中创建,但并非必须如此。例如,单元测试将assertEquals(5, calculator.add(2, 3))

  • mock 是对真实系统的模仿,通常用于单元测试。模拟通常在 MockitoEasyMock 等框架中创建,但并非必须如此。模拟是“测试替身”的一种形式 - 用于测试目的,您替换实际系统的代码的通用术语 - Martin Fowler 在一篇名为 "Mocks Aren't Stubs" 的文章中更准确地定义了它们。

  • 在编写单元测试时,您尝试测试单个单元,通常称为待测系统或简称SUT。每个测试可能会有不同的被测系统,重点是您在测试中测试的代码是您的真实的、实际的代码,而不是任何形式的模拟或假实现。 p>

  • 在复杂的应用程序中,您的类可能需要与其他可能编写或未测试的类协作,这些类被明确称为依赖项。任何给定的类或系统都可能有自己的依赖关系,并且可能是其他系统的依赖关系。模拟框架适用于模拟这些依赖项,而不是模拟被测系统。


对于您的示例,QAService 是您正在编写的主要类(被测系统),而 QAServiceTest 是该系统的单元测试。 不一定需要模拟。

假设 QAService 依赖于另一个尚未编写的类,称为“StorageService”。在编写 QAService 的测试之前,您不一定要等待 StorageService 工作,因此您可以使用 mock,而不是使用真正的 StorageService。同样,在名为 QAServiceTest 的 单元测试 中,您使用 real QAService 并模拟它的依赖关系 StorageService。

即使您没有编写 StorageService,您也可能对 QAService 将如何使用该类有所期待。也许你知道当你调用storageService.store(qaRecord) 时,它应该返回一个像101 这样的数字ID。即使代码不工作,您也可以创建一个 Mockito mockStorageService,并像这样准备它:

when(mockStorageService.store(expectedQARecord)).thenReturn(101);

现在假设在测试结束时,您要确保您正在测试的 QAService 方法绝对会调用storageService.delete(101)。 Mockito mockStorageService 会像这样检查:

verify(mockStorageService).delete(101);

通常不需要验证您使用when 所做的语句,因为除非被测系统正确调用模拟以获取该返回值(此处为 101),否则测试不太可能成功。

现在假设您编写了另一个名为 QAApplication 的代码块,您正在一个名为 QAApplicationTest 的单元测试中对其进行测试,该单元测试依赖于 QAService。您可能没有完成或测试 QAService,并且使用真正的 QAService 需要 StorageService,因此您可以在 单元测试中使用 mock QAService 和 real QAApplication 称为 QAApplicationTest。


因此,总而言之,模拟工作单元测试中模拟被测系统的依赖项时间>。在您的情况下,QAServiceTest 不需要模拟 QAService,但可以用于模拟 QAService 的依赖项。如果您确实需要一个模拟 QAService,那么在测试另一个 QAService 本身是依赖项的类时,您将需要它。

【讨论】:

  • @Jeff Bowman 感谢您的精彩解释。我知道通过接口模拟实现类是很常见的。但是如果我想模拟一个尚未实现的类的方法而不通过接口模拟呢?我是否向QAService 类添加一个空方法并在QAServiceTest 中模拟和存根该方法的预期逻辑?
  • @Justin 如何添加方法取决于您和您的开发团队,包括您如何同意接口更改、如何运行源代码控制以及发布周期。我将向 QAService 添加一个空的、记录在案的方法,在 QAServiceTest 中编写一个测试以帮助我了解未来的实现何时起作用,并在我模拟 QAService 的任何地方模拟/存根预期的行为(这几乎肯定不在 QAServiceTest 中)。
猜你喜欢
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
  • 2022-01-24
  • 2014-11-27
  • 2019-01-06
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多