当mock的XmlEndPoint对象被设置为这样的行为,“查找用户”操作就应该能找到用户、并组装出合法的结果对象:
Customer customer = identityService.findByEmail("gigix1980@gmail.com");
assertThat(customer.getFirstName(), equalTo("Jeff"));
assertThat(customer.getLastName(), equalTo("Xiong"));
userFoundResponse所引用的XML字符串中包含了用户信息,当XmlEndPoint返回这样一个字符串时,IdentityService就能把它转换成一个Customer对象。这样我们就验证了IdentityService(以及它内部所使用的其他对象)的功能。
第二种场景(“找不到用户”)的测试也与此相似:
@Test
public void shouldReturnNullWhenUserDoesNotExist() throws Exception {
when(xmlEndPoint.get(anyString())).thenReturn(
new XmlEndPointResponse(STATUS_NO_CONTENT, null));
Customer nonExistCustomer =
identityService.findByEmail("not.exist@gmail.com");
assertThat(nonExistCustomer, nullValue());
}
其他操作的测试也与此相似。
集成测试
有了上述两个层面的测试,我们已经能够对集成点的五个组件完全覆盖。但是请勿掉以轻心:100%测试覆盖率并不等于所有可能出错的地方都被覆盖。例如我们前述的两组测试就留下了两个重要的点没有得到验证:
1. 真实的服务所在的URL;
2. 真实的服务其行为是否与文档描述一致。
这两个点都是与真实服务直接相关的,必须结合真实服务来测试。另一方面,对这两个点的测试实际上描述功能重于验证功能:第一,外部服务很少变化,只要找到了正确的用法,在相当长的时间内不会改变;第二,外部服务如果出错(例如服务器宕机),从项目本身而言并没有修复的办法。所以真正触碰到被集成的外部服务的集成测试,其主要价值是准确描述外部服务的行为,提供一个可执行的、精确的文档。
为了提供这样一份文档,我们在集成测试中应该尽量避免使用应用程序内实现的集成点(例如前面出现过的IdentityService),因为如果程序出错,我们希望自动化测试能告诉我们:出错的究竟是被集成的外部服务,还是我们自己编写的程序。我更倾向于使用标准的、接近底层的库来直接访问外部服务:
System.out.println("=== 2. Find that user out ===");
GetMethod getToSearchUser = new GetMethod(
configuration.getUrlForSearchUser("gigix1980@gmail.com"));
getToSearchUser.setRequestHeader("Accept", "application/xml");
httpClient.executeMethod(getToSearchUser);
assertThat(getToSearchUser.getStatusCode(), equalTo(200));
System.out.println(getResponseBody(getToSearchUser));
可以看到,在这段测试中,我们直接使用Apache Commons HTTP Client来发起网络请求。对于应答结果我们也并不验证,只是确认服务仍然可用、并把应答正文(XML格式)直接打印出来以供参考。如前所述,集成测试主要是在描述外部服务的行为,而非验证外部服务的正确性。这种粒度的测试已经足够起到“可执行文档”的作用了。
持续集成
在上面介绍的几类测试中,只有集成测试会真正访问被集成的外部服务,因此集成测试也是耗时最长的。幸运的是,如前所述,集成测试只是用于描述外部服务,所有的功能验证都在网络端点测试(使用Moco)及其他组件的单元测试中覆盖,因此集成测试并不需要像其他测试那样频繁运行。
Maven已经对这种情形提供了支持。在Maven定义的构建生命周期[8]中,我们可以看到有“test”和“integration- test”两个阶段(phase)。而且在Maven项目网站上我们还可以看到一个叫“Failsafe”的插件[9],其中的介绍这样说道:
The Failsafe Plugin is designed to run integration tests while the Surefire Plugins is designed to run unit tests. The name (failsafe) was chosen both because it is a synonym of surefire and because it implies that when it fails, it does so in a safe way.
按照Maven的推荐,我们应该用Surefire插件来运行单元测试,用Failsafe插件来运行集成测试。为此,我们首先把所有集成测试放在“integration”包里,然后在pom.xml中配置Surefire插件不要执行这个包里的测试:
org.apache.maven.plugins
maven-surefire-plugin
${maven-surefire-plugin.version}
default-test
test
test
**/integration/**/*Test.java
再指定用Failsafe插件执行所有集成测试:
maven-failsafe-plugin
2.12
**/integration/**/*Test.java
failsafe-integration-tests
integration-test
integration-test
failsafe-verify
verify
verify
这时如果执行“mvn test”,集成测试已经不会运行;如果执行“mvn integration-test”,由于“integration-test”是在“test”之后的一个阶段,因此两组测试都会运行。这样我们就可以在持续集成服务器(例如Jenkins)上创建两个不同的构建任务:一个是提交构建,每次有代码修改时执行,其中不运行集成测试;另一个是完整构建,每天定时执行一次,其中运行集成测试。如此,我们便做到了速度与质量兼顾:平时提交时执行的构建足以覆盖我们开发的功能,执行速度飞快,而且不会因为外部服务宕机而失败;每日一次的完整构建覆盖了被集成的外部服务,确保我们足够及时地知晓外部服务是否仍然如我们期望地正常运行。
原文转自:http://www.infoq.com/cn/articles/enterprise-systems-integration-points