在为多个 Web 服务指定模拟时替代 instanceof 运算符

alternative to instanceof operator when specifying mocks for multiple web services

我正在编写端点单元测试,对于其中的大多数,应该模拟一个或多个外部 Web 服务。

起初,我在测试中创建模拟,这在端点测试仅使用一个外部服务时没问题,模拟创建基本上是一个班轮。

随着用例变得越来越复杂,我需要为单个端点测试模拟几个服务和异常。 我已将这些模拟创建放在所有扩展单个工厂并使用构建器模式的工厂后面。

在该基地工厂内有一个内部 class,我将其用作 MockWebServiceServer 的构建器。

protected class MultiStepMockBuilder {

    private List<Object> mockActions = new ArrayList<Object>();
    private WebServiceGatewaySupport gatewaySupport;

    protected MultiStepMockBuilder(WebServiceGatewaySupport gatewaySupport) {

        this.gatewaySupport = gatewaySupport;
    }

    protected MultiStepMockBuilder exception(RuntimeException exception) {

        mockActions.add(exception);

        return this;

    }

    protected MultiStepMockBuilder resource(Resource resource) {

        mockActions.add(resource);

        return this;
    }

    protected MockWebServiceServer build() {

        MockWebServiceServer server =  MockWebServiceServer.createServer(gatewaySupport);

        for(Object mock: mockActions) {

            if (mock instanceof RuntimeException) {

                server.expect(anything()).andRespond(withException((RuntimeException)mock));
            } 

            else if (mock instanceof Resource)
            {

                try 
                {
                    server.expect(anything()).andRespond(withSoapEnvelope((Resource) mock));

                } catch (IOException e) {e.printStackTrace();}
            }

            else 
                throw new RuntimeException("unusuported mock action");
        }

        return server;
    }
  }
}

所以我现在可以做这样的事情来创建模拟:

return new MultiStepMockBuilder(gatewaySupport).resource(success).exception(new WebServiceIOException("reserve timeout"))
                                               .resource(invalidMsisdn)
                                               .build();    

我在这个实现中遇到的问题是依赖于 instanceof 运算符,我从不在 equals 之外使用它。

在这种情况下是否有替代 instanceof 运算符的方法?从关于 instanceof 主题的问题中,每个人都认为它只能在 equals 中使用,因此我觉得这是 'dirty' 解决方案。

是否有 instanceof 运算符的替代方案,在 Spring 内或作为不同的设计,同时保留 fluent interface 用于模拟创建?

我对 Spring 的了解还不够,无法针对这个特定领域发表具体评论,但对我来说,这似乎只是一个设计问题。一般来说,当你面临使用instanceof时,意味着你需要知道类型,但你没有类型。通常情况下,我们可能需要重构以实现更内聚的设计来避免此类问题。

类型信息丢失的根源在模拟操作的 List 中,目前仅存储为 ListObject。解决这个问题的一种方法是查看 List 的类型,并考虑是否有更好的类型可以存储在 List 中,这可能会在以后帮助我们。所以我们最终可能会进行这样的重构。

private List<MockAction> mockActions = new ArrayList<MockAction>();

当然,然后我们必须决定 MockAction 实际上是什么,因为我们刚刚编造了它。也许是这样的:

interface MockAction {
  void performAction(MockWebServiceServer server);
}

所以,我们刚刚创建了这个 MockAction 接口,我们决定不让调用者执行操作 - 我们将把服务器传递给它并询问 MockAction 自行执行。如果我们这样做,那么就不需要 instanceof - 因为特定类型的 MockActions 会知道它们包含什么。

那么,我们需要什么类型的 MockAction

class ExceptionAction implements MockAction {
  private final Exception exception;

  private ExceptionAction(final Exception exception) {
    this.exception = exception;
  }

  public void performAction(final MockWebServiceServer server) {
    server.expect(anything()).andRespond(withException(exception);
  }

}

class ResourceAction implements MockAction {

  private final Resource resource;

  private ResourceAction(final Resource resource) {
    this.resource = resource;
  }

  public void performAction(final MockWebServiceServer server) {
    /* I've left out the exception handling */
    server.expect(anything()).andRespond(withSoapEnvelope(resource));
  }
}

好的,现在我们已经到了这一点,还有一些未解决的问题。

我们仍在向 MockAction 列表中添加例外情况 - 但我们需要更改添加方法以确保我们将正确的内容放入列表中。这些方法的新版本可能看起来像这样:

protected MultiStepMockBuilder exception(RuntimeException exception) {

    mockActions.add(new ExceptionAction(exception));

    return this;

}

protected MultiStepMockBuilder resource(Resource resource) {

    mockActions.add(new ResourceAction(resource));

    return this;
}

所以,现在我们的接口保持不变,但我们在将资源或异常添加到列表时对其进行包装,以便我们拥有稍后需要的类型特异性。

最后,我们需要重构实际进行调用的方法,现在看起来像这样 - 更简单、更清晰。

protected MockWebServiceServer build() {
    MockWebServiceServer server =  MockWebServiceServer.createServer(gatewaySupport);

    for(MockAction action: mockActions) {
        action.performAction(server);
    }
    return server;
}