目标:设计一个controller组件
设计接口:
Controller接受Request,分发给RequestHandler,并返回Response对象,有了这样一些描述,就可以编写一些初步的接口。
public interface Request {
String getName();
}
public interface Response {
}
public interface RequestHandler {
Response process(Request request) throws Exception;
}
public interface Controller {
Response processRequest(Request request);
//注册一个处理器(反转控制模式:不要呼叫我们,我们会呼叫你)
void addHandler(Request request,RequestHandler requestHandler);
}
Controller用到的设计模式:inversion of control
向Controller注册一个处理器是反转控制(依赖注入)模式的例子,“不要呼叫我们,我们会呼叫你”。对象(requestHandler)注册成事件的处理器。当事件发生时(processRequest),注册的对象的钩子方法(process)就会被调用。Inversion of control使得框架可以管理事件的声明周期,让开发者可以为框架事件插入自定义的处理器。
初步实现Controller类
public class DefaultController implements Controller{
private Map requestHandlers = new HashMap();//请求处理器的注册表
/**
* 为指定的请求获取RequestHandler
*/
public RequestHandler getHandler(Request request){
//如果requestHandler没有注册,则抛出异常
if(!requestHandlers.containsKey(request.getName())){
String message = "can't find handler for request name" + "["+request.getName() + "]";
throw new RuntimeException(message);
}
//返回一个合适的处理器
return (RequestHandler)requestHandlers.get(request.getName()); }
public void addHandler(Request request, RequestHandler requestHandler) {
//检查处理器的名字是否已经注册过了
if(requestHandlers.containsKey(request.getName())){
throw new RuntimeException("a request handler has already been registered for request name"+ "[" + request.getName() + "]");
}
else{
requestHandlers.put(request.getName(), requestHandler);
}
}
/**
* 核心方法:把请求分派至合适的处理器,并且把处理器的response传递给调用者
*/
public Response processRequest(Request request) {
Response response;
try{
//路由请求
response = getHandler(request).process(request);
}catch(Exception exception){
//错误处理
response = new ErrorResponse(request,exception);
}
return response;
}
}
测试DefaultController
单元测试的要点在于,一次只测试一个对象。为了创建一个单元测试,你需要两种类型的对象:你要测试的领域对象(DefaultController)和同被测试的对象交互的测试对象(Request,Response,RequestHandler)。
测试类的放置
可以做为共有类放在PACKAGE中
作为单元测试的内部类。(更适合TDD模式开发,测试先行,再编写代码)
以下就是经过改进的单元测试类
public class TestDefaultController extends TestCase {
private DefaultController controller;
private Request request;
private RequestHandler handler;
private Response response;
public TestDefaultController(String name) {
super(name);
}
protected void setUp() throws Exception {
controller = new DefaultController();
request = new TestRequest();
handler = new TestHandler();
controller.addHandler(request, handler);
}
protected void tearDown() throws Exception {
}
//作为内部类的测试类
private class TestRequest implements Request{
private static final String DEFAULT_NAME = "test";
private String name;
public TestRequest(String name){
this.name = name;
}
public TestRequest(){
this(DEFAULT_NAME);
}
public String getName(){
return name;
}
}
//作为内部类的测试类
private class TestHandler implements RequestHandler{
public Response process(Request request) throws Exception {
return new TestResponse();
}
}
//作为内部类的测试类
private class TestResponse implements Response{ }
//用于异常情况的RequestHandler
private class TestExceptionHandler implements RequestHandler{
public Response process(Request request) throws Exception {
throw new Exception("error processing request");
}
}
//测试注册
public void testAddHandler(){
RequestHandler handler2 = controller.getHandler(request);
assertSame(handler2,handler);
}
//测试请求处理
public void testProcessRequest(){
Response response = controller.processRequest(request);
assertNotNull("Must not return a null response",response);
assertEquals(TestResponse.class,response.getClass());//?????
}
//模拟异常条件
public void testProcessRequestAnswersErrorResponse(){
request = new TestRequest("testError");
TestExceptionHandler handler = new TestExceptionHandler();
controller.addHandler(request, handler);
response = controller.processRequest(request);
assertNotNull("must not return a null response",response);
assertEquals( ErrorResponse.class,response.getClass());
}
//测试抛出异常的方法
public void testGetHandlerNotDefined(){
request = new TestRequest("testNotDefined");
try{
controller.getHandler(request);
fail("An exception should be raised if the requested handler has not been registered" );
}
catch(RuntimeException expected){
assertTrue(true);
}
}
}
可以总结出测试异常的模式:
1. 插入应当抛出异常的语句
2. 在它后面加上fail语句(以防万一没有抛出异常)
3. 捕捉预期的异常,把异常起名为expected。这样就容易看出这个异常是预期的。
4. 一切正常。