1、Controller组件定义
controller(控制器):同用户交互,控制并管理每个请求的处理,可用于表现层模式,也可用于业务层模式
control主要做这些事:接受请求、对请求执行常用计算、选择合适的请求处理器、路由请求,以便处理器可以执行相关的业务逻辑、可能会提供一个顶层的处理器用于处理错误和异常
设计接口:4个对象,Request、Response、RequestHandle、Controller
它们的关系是:Controller接受Request,分发给RequestHandler,并返回Response
那么先来编写一些初步接口:
public interface Request {String getName();}
//返回一个请求的名称,以便区分不同的请求。
public interface Response { }
//Response对象封装的是稍后可以处理的东西,目前只要满足签名
public interface RequestHandler {
Response process(Request request)throws Exception;}
//定义一个能处理一个request并返回Response的反应RequestHandler。
//RequestHandler是一个辅助性组件,设计来做最“肮脏”的工作。它调用可能会抛出任何类型异常的类。
public interface Controller {
Response processRequest(Request request);
//定义一个用来处理收到的请求的方法。
void addHandler(Request request,RequestHandler requestHadnler);
//addHadndler方法允许扩展Controller而不必修改Java源代码。
}
一个简单的controller类的初步实现
public class DefaultController implements Controller{
//声明一个HashMap,用作请求处理器的注册表;
private Map requestHandlers=new HashMap();
//增加一个保护方法getHandler,用来为指定的请求获取HequestHandler.
protected RequestHandler getHandler(Request request) {
//如果RequestHandler没有注册,那么抛出RuntimeException异常 if(!this.requestHandlers.containsKey(request.getName())){
String message="Cannot find handler for request name"
+"["+request.getName()+"]";
throw new RuntimeException(message);
}
//返回适当的处理器来调用。
return (RequestHandler) this.requestHandlers.get(request.getName());
}
/*把请求分派到合适的处理器,并且把处理器的Response传回给调用者,如异常,捕获ErrorResponse*/
@Override
public Response processRequest(Request request) {
Response response;
try {response=getHandler(request).process(request);
} catch (Exception e) {
response=new ErrorResponse(request,e);}
return response;
}
@Override
public void addHandler(Request request, RequestHandler requestHandler) {
//检查处理器的名字是否已注册过,若已注册过,抛出异常。
if(this.requestHandlers.containsKey(request.getName())){
throw new RuntimeException("A request handler has already been "
+"registered for request name ["+request.getName()+"]");
}else{
this.requestHandlers.put(request.getName(), requestHandler);
}
}
}
ErrorResponse类的定义
public class ErrorResponse implements Response {
private Request originalRequest;
private Exception originalException;
public ErrorResponse(Request request, Exception exception) {
this.originalRequest = request;
this.originalException = exception;
}
public Request getOriginalRequest() {
return this.originalRequest;
}
public Exception getOriginalException() {
return this.originalException;
}
}
contrler骨架的需求与代码相关
需求 | 解决方案 |
接受请求 | public Response processRequest(Request request) |
选择处理器 | this.requestHandlers.get(request.getName()) |
路由请求 | response = getRequestHandler(request).process(request); |
错误处理 | 子类化ErrorRsponse |
Controller组件测试
public class TestDefaultController {
private DefaultController controller;
@Before
public void setUp(){
controller=new DefaultController();
}
@Test
public void testMethod(){
fail("Not yet implemented");
}
}
添加handler
1、添加一个RequestHandler,引用一个Request
2、获得到一个RequestHandler传递的是同一个Request
3、检查得到RequestHandler,是否就是添加的那一个
public class TestDefaultController {
....
//作为内部类的测试类
private class TestRequest implements Request{
public String getName(){
return "test";}
}
private class TestHandler implements RequestHandler{
public Response process(Request request) throws Exception {
return new TestResponse();}
}
private class TestResponse implements Response{
//暂时为空
}
}
在TestDefaultController添加testAddHander测试方法
@Test
public void testAddHandler() {
Request request = new TestRequest(); // 实例化对象
RequestHandler handler = new TestHandler(); // 实例化对象
// 下面是的代码是测试目标:controller( 待测对象)添加一个测试handler,
// 请注意controller是由setUp方法实例化来的。
controller.addHandler(request, handler);
// 把Handler读回到新的变量中。
RequestHandler handler2 = controller.getHandler(request);
// 对比两个对象是否是同一个对象。
assertSame("Handler we set in controller should be the same handler we get",handler2, handler);
}
controller的核心功能--处理请求
@Test
public void testProcessRequest(){
Request request=new TestRequest();
//创建测试对象并添加测试处理器
RequestHandler handler=new TestHandler();
controller.addHandler(request,handler);
Response response=controller.processRequest(request);
assertNotNull("Must not return a null response",response);
assertEquals(TestResponse.class,response.getClass());
}
•随着测试的深入,应该注意到测试应遵循如下的步骤:
–在开始测试时把环境设置成已知状态(创建对象、获取资源)。测试前的状态常称为Test fixture.
–调用待测试的方法。
–确认结果正确,通过调用一个或多个assert方法来实现。
分离出初始化逻辑
public class TestDefaultController {
private DefaultController controller;
private Request request;
private RequestHandler handler ;
@Before
public void setUp() {
controller = new DefaultController();
request = new TestRequest(); // 实例化对象
handler = new TestHandler(); // 实例化对象
controller.addHandler(request,handler);
}
改进testProcessRequest
•为了检查两个不同的对象是否具有相同的标识,需要提供对标识的定义。
下面对TestResponse进行重构
private class TestResponse implements Response {
private static final String NAME="Test";
public String getName(){ return NAME; }
public boolean equals(Object object){
boolean result=false;
if(object instanceof TestResponse){
result=((TestResponse )object).getName().equals(getName());
}
return result;
}
public int hashCode(){ return NAME.hashCode(); }
}
TestResponse有了自己的标识,现在就可以改进test方法
@Test
public void testProcessRequest(){
Response response=controller.processRequest(request);
assertNotNull("Must not return a null response",response);
//assertEquals(TestResponse.class,response.getClass()); 改进
assertEquals(new TestResponse(),response);
}
在上面的编码阶段中,我们把错误处理代码放到了基类中,processRequest 方法捕获了所有的异常并返回特殊响应
try {
response=getHandler(request).process(request);
} catch (Exception e) {
response=new ErrorResponse(request,e);
}
如何测试这个异常
为了测试错误情况,可以创建一个TestExceptionHandler内部类,用于抛出一个异常
private class TestExceptionHandler implements RequestHandler{
@Override
public Response process(Request request) throws Exception { throw new Exception("error processing request!");}
}
下面创建一个测试方法,注册这个Handler,并试图处理一个请求
//测试异常
@Test
public void testProcessRequestAnswertErrorResponse(){
TestRequest request =new TestRequest();
TestExceptionHandler handler=new TestExceptionHandler();
controller.addHandler(request, handler);
Response response =controller.processRequest(request);
assertNotNull("Must not return a null response",response);
assertEquals(ErrorResponse.class,response.getClass());
}
运行后发现报错
错误的主要原因有两个
1、需要为测试请求换个名字。因为fixture中已经有了一个叫“test”请求
2、可能需要在类中增加更多异常处理代码,以免在实际运行时抛出RuntimeException异常
现在改进代码:
对于第一点:可以试着用fixtrue中请求对象,而不是自己创建。不过这样做还是会出现同样的错误。如果从fixture中删除注册默认TsetRequest和TestHandler的代码,那么就会为其他方法强入了冗余
最好的方法是:修改TestRequest,让他可以以不同的名字实例化
修改TestRequest,可以以不同的名字实例化
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 this.name;
}
}
在tsetProcessRequestAnswersErrorResponse中调用新的构造方法,这样异常请求就不会和fixture冲突
@Test
public void testProcessRequestAnswertErrorResponse(){
//TestRequest request =new TestRequest();
TestRequest request =new TestRequest("testError");
TestExceptionHandler handler=new TestExceptionHandler();
controller.addHandler(request, handler);
Response response =controller.processRequest(request);
assertNotNull("Must not return a null response",response);
assertEquals(ErrorResponse.class,response.getClass());
}
①如果试图注册一个重名的请求,那么在测试时,会发出addHandler抛出了一个未说明的RuntimeException.
②再仔细观察一下代码,不难发现getHandler处理一个未被注册的Handler同样会抛出runtimeException.
③何如测试这两个方法呢?
可以这样做:
①插入应当抛出异常的语句
②在它后面加上fail语 句(以防万一没有抛出异常)
③捕捉预期的异常,把异常起名为expected,这样就很容易猜出这个异常是预期的
一切正常。
@Test
public void testGetHandlerNotDefined(){
TestRequest 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);
}
}
@Test
public void testAddRequestDuplicateName(){
TestRequest request=new TestRequest();
TestHandler handler=new TestHandler();
try {
controller.addHandler(request, handler);
fail("An exception should be raised if the requested"
+" handler has not been registered");
} catch (RuntimeException expected) {
assertTrue(true);
}
}
@Test(expected=RuntimeException.class)
public void testGetHandlerNotDefined(){
TestRequest request=new TestRequest("testNotDefined");
controller.getHandler(request);
}
@Test(expected=RuntimeException.class)
public void testAddRequestDuplicateName(){
TestRequest request=new TestRequest();
TestHandler handler=new TestHandler();
controller.addHandler(request, handler);
}
有时我们对代码的执行效率有一定的要求。如果执行时间超过了某个值,那么就认为这样的代码是有问题的。
在@Test这个annotation里这一参数:timeout可以用于这方面的测试。
@Test(timeout=130)
public void testProcessMultipleRequestTimeout(){
Request request;
Response response=new TestResponse();
RequestHandler handler=new TestHandler();
for(int i=0;i<99999;i++){
request =new TestRequest(String.valueOf(i));
controller.addHandler(request, handler);
response=controller.processRequest(request);
assertNotNull(response); assertNotSame(ErrorResponse.class,response.getClass());
}
}