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

Fat*_*ama 6 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);

   public final void handleRequest(Request request)
   {
     boolean handledByThisNode = this.handleRequestImpl(request);
     if (m_successor != null && !handledByThisNode)
     {
       m_successor.handleRequest(request);
     }
   }
 }
Run Code Online (Sandbox Code Playgroud)

似乎很常见。但是如何使用受保护的抽象方法对此进行测试?解决此问题的方法似乎是:

  1. 实现仅测试的子类,Handler该子类实现抽象方法。这似乎对测试维护不利。
  2. 将抽象方法的可见性更改为public,但是我不需要更改SUT以适应测试。
  3. 将抽象类视为足够简单,无需进行单元测试。嗯
  4. handleRequest在一个或多个具体子类上对该方法实施单元测试。但这似乎不是组织测试的明智方法。
  5. 有某种方式可以使用模拟对象吗?我已经尝试过Mockito,但似乎无法绕开受保护的可见性。

我已经读过[ 1 ],这种测试问题意味着设计是错误的,并建议使用组合而不是继承。我现在正在尝试此操作,但是奇怪的是,此模式的推荐实现存在此问题,但是我无法找到有关对其进行单元测试的任何建议。

更新:我已将抽象类替换为依赖关系反转,如图所示,现在可以使用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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*mme 2

如果你使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这将验证当第一个处理程序的方法返回 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用这种方式模拟它(或者使用几乎任何模拟框架,因为你没有受保护的方法):

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在此解决方案中,您还可以使 Handler 在 Callable 之后继承,然后您可以将其包装在可能具有后继者的任何其他可调用对象上,并使用处理程序而不是原始可调用对象;它更加灵活。