Java 如何以可测试的方式实施责任链?

Java 如何以可测试的方式实施责任链?,java,unit-testing,chain-of-responsibility,Java,Unit Testing,Chain Of Responsibility,我想实施责任链模式,解决“断链”问题如下: public abstract class Handler{ private Handler m_successor; public void setSuccessor(Handler successor) { m_successor = successor; } protected abstract boolean handleRequestImpl(Request request); publ

我想实施责任链模式,解决“断链”问题如下:

 public abstract class Handler{

   private Handler m_successor;

   public void setSuccessor(Handler successor)
   {
     m_successor = successor;
   }

   protected abstract boolean handleRequestImpl(Request request);

   public final void handleRequest(Request request)
   {
     boolean handledByThisNode = this.handleRequestImpl(request);
     if (m_successor != null && !handledByThisNode)
     {
       m_successor.handleRequest(request);
     }
   }
 }
这似乎是一个足够普遍的方法。但是,如何使用受保护的抽象方法来测试这个问题呢?处理这一问题的方法似乎是:

  • 实现实现抽象方法的
    Handler
    的仅测试子类。这似乎不利于测试维护
  • 将抽象方法的可见性更改为public,但我不需要更改SUT来适应测试
  • 将抽象类视为足够简单,不需要单元测试。嗯
  • 在一个或多个具体子类上为
    handleRequest
    方法执行单元测试。但这似乎不是一种合理的测试组织方式
  • 有没有办法使用模拟对象?我试过Mockito,但似乎无法避开受保护的可见性
  • 我读过[]这类测试问题暗示设计是错误的,建议使用组合而不是继承。我现在正在尝试,但奇怪的是,这个模式的推荐实现有这个问题,但是我找不到任何关于单元测试的建议

    更新: 如图所示,我已将抽象类替换为依赖项反转,现在可以使用Mockito轻松地进行测试。看起来仍然像是责任链。。。我错过什么了吗

    // Implement a concrete class instead
    public class ChainLink {
    
      // Successor as before, but with new class type
      private ChainLink m_successor;
    
      // New type, RequestHandler
      private RequestHandler m_handler;
    
      // Constructor, with RequestHandler injected
      public ChainLink(RequestHandler m_handler) {
        this.m_handler = m_handler;
      }
    
      // Setter as before, but with new class type
      public void setSuccessor(ChainLink successor) {
        m_successor = successor;
      }
    
      public final void handleRequest(Request request) {
        boolean handledByThisNode = m_handler.handleRequest(request);
        if (m_successor != null && !handledByThisNode) {
          m_successor.handleRequest(request);
        }
      }
    }
    

    我会选择选项1。这个伪子类足够简单,你可以把它放在你的tets类中。仅测试子类没有问题。

    如果使用PowerMock+Mockito,可以编写类似以下内容的测试:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Tests.class)
    public class Tests {
        @Test
        public void testHandledByFirst() throws Exception {
            Request req = ...;
            Handler h1 = mock(Handler.class);
            Handler h2 = mock(Handler.class);
    
            when(h1, "setSuccessor", h2).thenCallRealMethod();
            when(h1, "handleRequestImpl", req).thenReturn(true);
    
            h1.setSuccessor(h2);
            h1.handleRequest(req);
            verify(h2, times(0)).handleRequest(req);
        }
    
        @Test
        public void testHandledBySecond() throws Exception {
            Request req = ...;
            Handler h1 = mock(Handler.class);
            Handler h2 = mock(Handler.class);
    
            when(h1, "setSuccessor", h2).thenCallRealMethod();
            when(h1, "handleRequestImpl", req).thenReturn(false);
            h1.setSuccessor(h2);
    
            h1.handleRequest(req);
            verify(h2, times(1)).handleRequest(req);
        }
    }
    
    这将验证第二个处理程序的方法在第一个处理程序返回false时被调用,而在第一个处理程序返回true时未被调用

    另一种选择是遵循众所周知的“重组合轻继承”的规则,将类更改为如下内容:

    public interface Callable {
        public boolean call(Request request);
    }
    
    public class Handler {
        private Callable thisCallable;
        private Callable nextCallable;
    
        public Handler(Callable thisCallable, Callable nextCallable) {
            this.thisCallable = thisCallable;
            this.nextCallable = nextCallable;
        }
    
        public boolean handle(Request request) {
            return thisCallable.call(request) 
                || (nextCallable != null && nextCallable.call(request));
        }
    }
    
    然后您可以用这种方式模拟它(或者使用几乎所有的模拟框架,因为您没有受保护的方法):


    在这个解决方案中,您还可以使处理程序在可调用之后继承,然后您可以将其包装到任何其他可能有后续调用的可调用上,并使用该处理程序而不是原始的可调用处理程序;它灵活得多。

    我确实想知道我是否把事情复杂化了。然而,虽然给出的示例很简单,但我认为抽象类会随着时间的推移变得更加复杂。现在实现一个子类已经足够简单了,但是如果它发生了变化呢?好吧,如果抽象基类随着时间的推移变得更复杂,那么设计可能有问题。检查您的代码和设计是否符合原则。大型复杂的基类通常是违反可靠原则的标志,违反这些原则通常会导致无法维护的软件。当测试很难写的时候,这也常常是违反SOLID的迹象。谢谢——这就是我思考的方向,我用另一种方法编辑了我的问题。它看起来仍然像是责任链,但更倾向于组合而不是继承。我想知道为什么不首先使用这种方法来描述责任链。因此,明确地说,PowerMock可以存根抽象受保护的方法吗?是的。我还编辑了我的答案,以展示另一种方法(通过稍微不同的方式实现您的类),在这种方法中,您不必完全处理受保护的方法。谢谢-我同时用类似的东西编辑了我的问题。这证实了我的想法,所以我会将此标记为已回答!干杯
    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Tests.class)
    public class Tests {
        @Test
        public void testHandledByFirst() throws Exception {
            Request req = ...;
            Callable c1 = mock(Callable.class);
            Callable c2 = mock(Callable.class);
            Handler handler = new Handler(c1, c2);
    
            when(c1.call(req)).thenReturn(true);
    
            handler.handle(req);
    
            verify(c1, times(1)).call(req);
            verify(c2, times(0)).call(req);
        }
    
        @Test
        public void testHandledBySecond() throws Exception {
            Request req = ...;
            Callable c1 = mock(Callable.class);
            Callable c2 = mock(Callable.class);
            Handler handler = new Handler(c1, c2);
    
            when(c1.call(req)).thenReturn(false);
    
            handler.handle(req);
    
            verify(c1, times(1)).call(req);
            verify(c2, times(1)).call(req);
        }
    }