单元测试困境

时间:2016-03-10 06:16:26

标签: java unit-testing tdd mockito guice

我正在开发一个类,它存储了一些会话信息以与第三方API进行通信。所以,基本上它有很多行为和很少的状态来维护。这是其中一种方法:

  public LineItem getLineItem(
      String networkId, String lineItemId) throws ApiException_Exception {
    LineItem lineItem = null;
    session.setCode(networkId); 
    LineItemServiceInterface lineItemService = servicesInterface.lineItemService(session);
    StatementBuilder statementBuilder =
        new StatementBuilder()
            .where("id = " + lineItemId.trim())
            .orderBy("id ASC")
            .limit(StatementBuilder.SUGGESTED_PAGE_LIMIT);
    LineItemPage lineItemPage =
        lineItemService.getLineItemsByStatement(statementBuilder.toStatement());
    if (lineItemPage != null && lineItemPage.getResults() != null) {
      lineItem = lineItemPage.getResults().get(0);
    }
    return lineItem;
  }

我坚持如何测试这个方法,它对第三方对象有太多的隐式依赖。这些对象很难自己创建。另一个大问题是getLineItemByStatement在场景后面进行网络调用(SOAP)。

就我而言,我正在尝试模拟外部服务并检查服务是否正在请求具有正确Statement的数据,除了我无法执行任何操作,因为我的对象中没有状态更改大多数对象交互都是第三方。

问题

这些情景中的大部分混淆是我的班级应该了解合作者的数量?我的测试需要知道我的测试方法使用的对象有多少?

示例:

  @Test
  public void shouldGetLineItem() throws ApiException_Exception {
    when(servicesInterface.lineItemService(dfpSession)).thenReturn(mockLineItemService);
    dfpClient.getLineItem("123", "123");
    Statement mockStatement = mock(Statement.class);
    Statement statement =
        new StatementBuilder()
            .where("id = 123")
            .orderBy("id ASC")
            .limit(StatementBuilder.SUGGESTED_PAGE_LIMIT)
            .toStatement();

    verify(dfpSession).setNetworkCode("123");
    verify(mockLineItemService).getLineItemsByStatement(isA(Statement.class));
  }

我们可以看到我的测试对我测试的方法了解得太多了。

更新1

经过一段时间后,我发现单元测试我的课程变得太难了,因为LineItem的引用分散在各处,因为LineItem与其他对象有许多深层链接,很难创建自己的对象因此,我决定创建一个包含我的应用程序相关细节的域模型。

  public LineItemDescription getLineItem(String networkId, String lineItemId)
      throws ApiException_Exception {
    dfpSession.setNetworkCode(networkId);
    LineItemServiceInterface lineItemService = servicesInterface.lineItemService(dfpSession);
    return buildLineItemDescription(
        getFirstItemFromPage(lineItemService.getLineItemsByStatement(buildStatement(lineItemId))));
  }

2 个答案:

答案 0 :(得分:2)

基本方法

这看起来像是我考虑有限价值的单元测试的情况。看起来你真正想要的可能是测试,它确保正确调用SOAP服务,并根据需要转换结果。所以我会去进行集成测试。测试将调用/ a SOAP服务,但我会嘲笑它。即您设置了一个服务,您可以在其中指定它将如何响应您的请求。然后调用方法,并检查结果。

需要考虑的其他事项 我假设您已经使用单元测试测试了该方法中使用的所有内容。

让代码的读者感到困惑并且可能使测试变得更加困难的一件事就是对networkid进行一些奇怪的处理。它在session中设置为'Code',它本身很奇怪,但它没有被使用。实际上,我认为某些东西正在从会话中获得该值,但这基本上是全局状态,并且很难推断出正在发生的事情。如果你需要它处于全局状态以避免在整个地方传递它,在一个单独的方法中移动该部分(或在新方法中提取其余部分),这样你就可以测试其他所有内容,而不需要改变全局状态。或者将它夸大地传递给实际需要它的方法。

答案 1 :(得分:1)

我首先重构方法(只需提取私有方法并移动东西),看起来像这样:

public LineItem getLineItem(String networkId, String lineItemId) throws ApiException_Exception {
    LineItemServiceInterface lineItemService = getLineItemServiceForNetwork(networkId);
    return getFirstItemFromPage(lineItemService.getLineItemsByStatement(buildStatement(lineItemId)));
}

看看这个版本的代码,我们发现这个方法至少有一个太多的职责。例如,创建和设置LineItemServiceInterface应该被卸载到可以被模拟的协作者,或者它应该由调用者而不是networkId提供(因为如果调用者没有'}提供您必须的服务来模拟服务提供商协作者以返回另一个模拟)。如果将LineItemServiceInterface的创建卸载到另一个类太痛苦(因为有很多遗留的依赖),快速而又脏的替代方法是使getLineItemServiceInterface()受保护或包级别覆盖它以返回用于测试的子类中的模拟。

因此,正常使用" case你对这个方法的测试需要1)stub(使用Mockito.when())当模拟的服务接口收到一个给定lineItemId的正确形成的语句时,它返回一个包含一个LineItem的列表实例,然后2)检查LineItem是否返回了getLineItem()实例。然后,您知道getLineItem()正确调用了服务并正确提取结果。

顺便说一句,你不需要嘲笑Statement。您需要编写matcher来验证传递给Statement的{​​{1}}实例是否使用正确的ID值,排序和限制正确制定。如果getLineItemsByStatement()是第三方类,它不允许访问此类信息(直接通过getter或间接通过生成的查询代码),您可以考虑将Statement创建卸载到另一个注入的协作者,你会模拟这个测试,然后你在其他地方使用针对真实服务的集成测试验证协作者。

编辑:基于评论,这里是编写测试的粗略示例,假设进一步重构以卸载Statement设置协作者:

LineItemServiceInterface

测试中的变量@Test public void shouldGetLineItem() throws ApiException_Exception { when(lineItemserviceProviderMock.getLineItemService(NETWORK_ID, dfpSession)).thenReturn(mockLineItemService); when(mockLineItemService.getLineItemsByStatement(argThat(statementMatcher)).thenReturn(LIST_WITH_EXPECTED_LINE_ITEM); LineItem expectedResult = dfpClient.getLineItem(NETWORK_ID, LINE_ITEM_ID); assertEquals(EXPECTED_LINE_ITEM, expectedResult); } 看起来大致如下:

statementMatcher