我想实现责任链模式,按如下方式处理“断开的链接”问题:
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);
}
}
}
似乎是一种很常见的方法。但是如何用 protected 抽象方法来测试呢?处理这个问题的方法似乎是:
- 实现
Handler
的一个仅测试子类,它实现了抽象方法。这似乎不利于测试维护。 - 将抽象方法的可见性更改为公开,但我不需要更改 SUT 来适应测试。
- 认为抽象类足够简单,不需要单元测试。嗯。
- 对一个或多个具体子类的
handleRequest
方法实现单元测试。但这似乎不是组织测试的明智方式。 - 有什么办法可以使用模拟对象吗?我试过 Mockito,但似乎无法绕过 protected 可见性。
我读过[ 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);
}
}
}
最佳答案
如果你使用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));
}
}
然后你可以用这种方式模拟它(或者使用几乎任何模拟框架,因为你没有 protected 方法):
@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);
}
}
在此解决方案中,您还可以使 Handler 在 Callable 之后继承,然后您可以将其包装在任何其他可能具有后继者的可调用对象上,并使用处理程序而不是原始可调用对象;它更加灵活。
关于java - 如何以可测试的方式实现责任链?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8944976/