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)
似乎很常见。但是如何使用受保护的抽象方法对此进行测试?解决此问题的方法似乎是:
Handler该子类实现抽象方法。这似乎对测试维护不利。handleRequest在一个或多个具体子类上对该方法实施单元测试。但这似乎不是组织测试的明智方法。我已经读过[ 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)
如果你使用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 之后继承,然后您可以将其包装在可能具有后继者的任何其他可调用对象上,并使用处理程序而不是原始可调用对象;它更加灵活。