【问题标题】:alternative to instanceof operator when specifying mocks for multiple web services为多个 Web 服务指定模拟时替代 instanceof 运算符
【发布时间】:2015-08-08 18:23:02
【问题描述】:

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

起初,我在测试中创建模拟,当端点测试仅使用一个外部服务时,模拟创建基本上是一个衬垫。

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

在该基础工厂中有一个内部类,我将其用作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 中使用,因此我觉得这是“肮脏”的解决方案。

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

【问题讨论】:

    标签: java spring unit-testing instanceof


    【解决方案1】:

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

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

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

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

    interface MockAction {
      void performAction(MockWebServiceServer server);
    }
    

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

    那么,我们需要什么类型的MockActions?

    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));
      }
    }
    

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

    我们仍在向MockActions 列表添加例外 - 但我们需要更改添加方法以确保我们将正确的内容放入列表中。这些方法的新版本可能如下所示:

    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;
    }
    

    【讨论】:

    • 这实际上是个好主意,将异常和资源包装在实现通用接口的容器中。我通常会让我的自定义对象实现共享接口以避免实例,但是由于 RuntimeException 和资源是 java 的东西我不能这样做。
    猜你喜欢
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多