企业系统集成点测试策略(4)

发表于:2013-08-29来源:InfoQ作者:熊节点击数: 标签:集成测试
对已有系统的重构 如果一开始就按照前文所述的模式来设计集成点,自然很容易保障系统的可测试性;但如果一开始没有做好设计,没有抽象出网络端点的

  对已有系统的重构

  如果一开始就按照前文所述的模式来设计集成点,自然很容易保障系统的可测试性;但如果一开始没有做好设计,没有抽象出“网络端点”的概念,而是把网络访问的逻辑与其他逻辑耦合在一起,自然也就难以写出专门针对网络访问的测试,从而使得大量测试会发起真实的网络访问,使构建变得缓慢而不可靠。

  下面就是一段典型的代码结构,其中杂糅了几种不同的职责:准备请求正文;发起网络请求;处理应答内容。

  PostMethod postMethod = getPostMethod(

  velocityContext, templateName, soapAction);

  new HttpClient().executeMethod(postMethod);

  String responseBodyAsString = postMethod.getResponseBodyAsString();

  if (responseBodyAsString.contains("faultstring")) {

  throw new WmbException();

  }

  Document document;

  try {

  LOGGER.info("request:\n" + responseBodyAsString);

  document = DocumentHelper.parseText(responseBodyAsString);

  } catch (Exception e) {

  throw new WmbParseException(

  e.getMessage() + "\nresponse:\n" + responseBodyAsString);

  }

  return document;

  针对每个要集成的服务方法,类似的代码结构都会出现,从而出现了“重复代码”的坏味道。由于准备请求正文、处理应答内容等逻辑各处不同(例如上面的代码使用Velocity[10]来生成请求正文、使用JDOM[11]来解析应答),这里的重复并不那么直观,自动化的代码检视工具(例如Sonar)通常也不能发现。因此第一步的重构是让重复的结构浮现出来。

  使用抽取函数(Extract Method)、添加参数(Add Parameter)、删除参数(Remove Parameter)等重构手法,我们可以把上述代码整理成如下形状:

  // 1. prepare request body

  String requestBody = renderTemplate(velocityContext, templateName);

  // 2. execute a post method and get back response body

  PostMethod postMethod = getPostMethod(soapAction, requestBody);

  new HttpClient().executeMethod(postMethod);

  String responseBody = postMethod.getResponseBodyAsString();

  if (responseBodyAsString.contains("faultstring")) {

  throw new WmbException();

  }

  // 3. deal with response body

  Document document = parseResponse(responseBody);

  return document;

  这时,第2段代码(使用预先准备好的请求正文执行一个POST请求,并拿回应答正文)的重复就变得明显了。《重构》对这种情况做了介绍[12]:

  如果两个毫不相关的类出现Duplicated Code,你应该考虑对其中一个使用Extract Class,将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

  这正是我们面对的情况,也正是“网络端点”这个概念应该出现的时候。使用抽取函数和抽取类(Extract Class)的重构手法,我们就能得到名为SOAPEndPoint的类:

  public class SOAPEndPoint {

  public String post(String soapAction, String requestBody) {

  PostMethod postMethod = getPostMethod(soapAction, requestBody);

  new HttpClient().executeMethod(postMethod);

  String responseBody = postMethod.getResponseBodyAsString();

  if (responseBodyAsString.contains("faultstring")) {

  throw new WmbException();

  }

  return responseBody;

  }

  原来的代码变为使用这个新的类:

  // 1. prepare request body

  String requestBody = renderTemplate(velocityContext, templateName);

  // 2. execute a post method and get back response body

  // soapEndPoint is dependency injected by Spring Framework

  String responseBody = soapEndPoint.post(soapAction, requestBody);

  // 3. deal with response body

  Document document = parseResponse(responseBody);

  return document;

  再按照前文所述的测试策略,使用Moco给SOAPEndPoint类添加测试。可以看到,SOAPEndPoint的逻辑相当简单:把指定的请求文本POST到指定的URL;如果应答文本包含“faultstring”字符串,则抛出异常;否则直接返回应答文本。尽管名为 “SOAPEndPoint”,post这个方法其实根本不关心请求与应答是否符合SOAP协议,因此在测试这个方法时我们也不需要让Moco返回符合 SOAP协议的应答文本,只要覆盖应答中是否包含“faultstring”字符串的两种情况即可。

  读者或许会问:既然post方法并不介意请求与应答正文是否符合SOAP协议,为什么这个类叫SOAPEndPoint?答案是:在本文没有给出实现代码的getPostMethod方法中,我们需要填入一些HTTP头信息,这些信息是与提供Web Services的被集成服务相关的。这些HTTP头信息(例如应用程序的身份认证、Content-Type等)适用于所有服务方法,因此可以抽取到通用的getPostMethod方法中。

  随后,我们可以编写一些描述性的集成测试,并用mock的方式使所有“使用SOAPEndPoint的类”的测试不再发起网络请求。至此,我们就完成了对已有的集成点的重构,并得到了一组符合前文所述的测试策略的测试用例。当然读者可以继续重构,将请求构造器与应答解析器也分离出来,在此不再赘述。

原文转自:http://www.infoq.com/cn/articles/enterprise-systems-integration-points