再指定用Failsafe插件执行所有集成测试:
<plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.12</version> <configuration> <includes> <include>**/integration/**/*Test.java</include> </includes> </configuration> <executions> <execution> <id>failsafe-integration-tests</id> <phase>integration-test</phase> <goals> <goal>integration-test</goal> </goals> </execution> <execution> <id>failsafe-verify</id> <phase>verify</phase> <goals> <goal>verify</goal> </goals> </execution> </executions> </plugin> |
这时如果执行“mvn test”,集成测试已经不会运行;如果执行“mvn integration-test”,由于“integration-test”是在“test”之后的一个阶段,因此两组测试都会运行。这样我们就可以在持续集成服务器(例如Jenkins)上创建两个不同的构建任务:一个是提交构建,每次有代码修改时执行,其中不运行集成测试;另一个是完整构建,每天定时执行一次,其中运行集成测试。如此,我们便做到了速度与质量兼顾:平时提交时执行的构建足以覆盖我们开发的功能,执行速度飞快,而且不会因为外部服务宕机而失败;每日一次的完整构建覆盖了被集成的外部服务,确保我们足够及时地知晓外部服务是否仍然如我们期望地正常运行。
对已有系统的重构
如果一开始就按照前文所述的模式来设计集成点,自然很容易保障系统的可测试性;但如果一开始没有做好设计,没有抽象出“网络端点”的概念,而是把网络访问的逻辑与其他逻辑耦合在一起,自然也就难以写出专门针对网络访问的测试,从而使得大量测试会发起真实的网络访问,使构建变得缓慢而不可靠。
下面就是一段典型的代码结构,其中杂糅了几种不同的职责:准备请求正文;发起网络请求;处理应答内容。
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来生成请求正文、使用JDOM来解析应答),这里的重复并不那么直观,自动化的代码检视工具(例如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请求,并拿回应答正文)的重复就变得明显了。《重构》对这种情况做了介绍:
如果两个毫不相关的类出现Duplicated Code,你应该考虑对其中一个使用Extract Class,将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。