當mock的XmlEndPoint對象被設置為這樣的行為,“查找用戶(hù)”操作就應該能找到用戶(hù)、并組裝出合法的結果對象:
Customer customer = identityService.findByEmail("gigix1980@gmail.com");
assertThat(customer.getFirstName(), equalTo("Jeff"));
assertThat(customer.getLastName(), equalTo("Xiong"));
userFoundResponse所引用的XML字符串中包含了用戶(hù)信息,當XmlEndPoint返回這樣一個(gè)字符串時(shí),IdentityService就能把它轉換成一個(gè)Customer對象。這樣我們就驗證了IdentityService(以及它內部所使用的其他對象)的功能。
第二種場(chǎng)景(“找不到用戶(hù)”)的測試也與此相似:
@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());
}
其他操作的測試也與此相似。
集成測試
有了上述兩個(gè)層面的測試,我們已經(jīng)能夠對集成點(diǎn)的五個(gè)組件完全覆蓋。但是請勿掉以輕心:100%測試覆蓋率并不等于所有可能出錯的地方都被覆蓋。例如我們前述的兩組測試就留下了兩個(gè)重要的點(diǎn)沒(méi)有得到驗證:
1. 真實(shí)的服務(wù)所在的URL;
2. 真實(shí)的服務(wù)其行為是否與文檔描述一致。
這兩個(gè)點(diǎn)都是與真實(shí)服務(wù)直接相關(guān)的,必須結合真實(shí)服務(wù)來(lái)測試。另一方面,對這兩個(gè)點(diǎn)的測試實(shí)際上描述功能重于驗證功能:第一,外部服務(wù)很少變化,只要找到了正確的用法,在相當長(cháng)的時(shí)間內不會(huì )改變;第二,外部服務(wù)如果出錯(例如服務(wù)器宕機),從項目本身而言并沒(méi)有修復的辦法。所以真正觸碰到被集成的外部服務(wù)的集成測試,其主要價(jià)值是準確描述外部服務(wù)的行為,提供一個(gè)可執行的、精確的文檔。
為了提供這樣一份文檔,我們在集成測試中應該盡量避免使用應用程序內實(shí)現的集成點(diǎn)(例如前面出現過(guò)的IdentityService),因為如果程序出錯,我們希望自動(dòng)化測試能告訴我們:出錯的究竟是被集成的外部服務(wù),還是我們自己編寫(xiě)的程序。我更傾向于使用標準的、接近底層的庫來(lái)直接訪(fǎng)問(wèn)外部服務(wù):
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來(lái)發(fā)起網(wǎng)絡(luò )請求。對于應答結果我們也并不驗證,只是確認服務(wù)仍然可用、并把應答正文(XML格式)直接打印出來(lái)以供參考。如前所述,集成測試主要是在描述外部服務(wù)的行為,而非驗證外部服務(wù)的正確性。這種粒度的測試已經(jīng)足夠起到“可執行文檔”的作用了。
持續集成
在上面介紹的幾類(lèi)測試中,只有集成測試會(huì )真正訪(fǎng)問(wèn)被集成的外部服務(wù),因此集成測試也是耗時(shí)最長(cháng)的。幸運的是,如前所述,集成測試只是用于描述外部服務(wù),所有的功能驗證都在網(wǎng)絡(luò )端點(diǎn)測試(使用Moco)及其他組件的單元測試中覆蓋,因此集成測試并不需要像其他測試那樣頻繁運行。
Maven已經(jīng)對這種情形提供了支持。在Maven定義的構建生命周期[8]中,我們可以看到有“test”和“integration- test”兩個(gè)階段(phase)。而且在Maven項目網(wǎng)站上我們還可以看到一個(gè)叫“Failsafe”的插件[9],其中的介紹這樣說(shuō)道:
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插件來(lái)運行單元測試,用Failsafe插件來(lái)運行集成測試。為此,我們首先把所有集成測試放在“integration”包里,然后在pom.xml中配置Surefire插件不要執行這個(gè)包里的測試:
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
這時(shí)如果執行“mvn test”,集成測試已經(jīng)不會(huì )運行;如果執行“mvn integration-test”,由于“integration-test”是在“test”之后的一個(gè)階段,因此兩組測試都會(huì )運行。這樣我們就可以在持續集成服務(wù)器(例如Jenkins)上創(chuàng )建兩個(gè)不同的構建任務(wù):一個(gè)是提交構建,每次有代碼修改時(shí)執行,其中不運行集成測試;另一個(gè)是完整構建,每天定時(shí)執行一次,其中運行集成測試。如此,我們便做到了速度與質(zhì)量兼顧:平時(shí)提交時(shí)執行的構建足以覆蓋我們開(kāi)發(fā)的功能,執行速度飛快,而且不會(huì )因為外部服務(wù)宕機而失敗;每日一次的完整構建覆蓋了被集成的外部服務(wù),確保我們足夠及時(shí)地知曉外部服務(wù)是否仍然如我們期望地正常運行。
原文轉自:http://www.infoq.com/cn/articles/enterprise-systems-integration-points