基于SpringBoot為微服務(wù)架構(gòu)編寫端到端測試
本文來源:http://r6d.cn/HwyV
微服務(wù)架構(gòu)的一個主要方面是應(yīng)用程序形成為松散耦合的服務(wù)的集合,每個服務(wù)可以獨(dú)立地部署并且通過某種輕型協(xié)議相互通信。

現(xiàn)在假設(shè)您要為Cart Service編寫端到端測試。您很快就會發(fā)現(xiàn)它并不容易,讓我列舉一些原因:
購物車服務(wù)需要知道如何啟動定價服務(wù),目錄服務(wù)和MongoDB(如果您想要涉及前端以及Coolstore GW和WebUI)。 購物車服務(wù)需要為兩種外部服務(wù)準(zhǔn)備一些數(shù)據(jù)(固定裝置)。 您使用網(wǎng)絡(luò)與服務(wù)進(jìn)行通信。可能會發(fā)生一些測試失敗,不是因為真正的故障,而是因為基礎(chǔ)設(shè)施問題或其他服務(wù)有任何錯誤。因此,這些測試的可能性變得不穩(wěn)定并且開始失敗,因為當(dāng)前服務(wù)中引入的任何更改都更高。 在更復(fù)雜的情況下,在成本(部署到云),時間(啟動所有基礎(chǔ)架構(gòu)和服務(wù))和維護(hù)時間方面,運(yùn)行這些測試可能會很昂貴。 很難在開發(fā)人員計算機(jī)中運(yùn)行它們,因為您需要在計算機(jī)上安裝所有部件。
因此,端到端測試不是測試微服務(wù)的最佳方法,但您仍需要一種從服務(wù)的開始到結(jié)束進(jìn)行測試的方法。
有必要找到一種“模擬”這些外部依賴關(guān)系的方法,而不必注入任何模擬對象。我們需要做的是欺騙被測服務(wù),因此它確實認(rèn)為它正在與真實的外部服務(wù)進(jìn)行通信,而實際上并非如此。
允許我們這樣做的方法是Service Virtualiztion。服務(wù)虛擬化是一種模擬組件應(yīng)用程序(如基于API)的行為的方法。
您可以將服務(wù)虛擬化視為您過去在OOP中實現(xiàn)的模擬方法,而不是在對象級別進(jìn)行模擬,而是在服務(wù)級別進(jìn)行模擬。這是對企業(yè)的嘲弄。
有很多服務(wù)虛擬化工具,但根據(jù)我的經(jīng)驗,在JVM生態(tài)系統(tǒng)中,更好的工具之一是Hoverfly。
讓我們看看Cart Service的“端到端”測試是怎樣的。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment?=?SpringBootTest.WebEnvironment.RANDOM_PORT,
????properties?=?"CATALOG_ENDPOINT=catalog")
public?class?CartServiceBoundaryTest?{
????@Autowired
????private?TestRestTemplate?restTemplate;
????@ClassRule
????public?static?HoverflyRule?hoverflyRule?=?HoverflyRule.inSimulationMode(dsl(
????????service("catalog")
????????????.get("/api/products")
????????????.willReturn(success(json(ProductsObjectMother.createVehicleProducts())))
????));
????@Test
????public?void?should_add_item_to_shopping_cart()?{
????????final?ShoppingCart?shoppingCart?=?this.restTemplate.postForObject("/api/cart/1/1111/2",?"",?ShoppingCart.class);
????????assertThat(shoppingCart)
????????????.returns(0.0,?ShoppingCart::getCartItemPromoSavings)
????????????.returns(2000.0,?ShoppingCart::getCartItemTotal)
????????????.returns(-10.99,?ShoppingCart::getShippingPromoSavings)
????????????.returns(2000.0,?ShoppingCart::getCartTotal)
????????????.extracting(ShoppingCart::getShoppingCartItemList)
????????????.hasSize(1);
????}
}
這個服務(wù)是使用Spring Boot實現(xiàn)的,所以我們使用的是Spring Boot Test框架。這里的重要部分是使用CATALOG_ENDPOINT屬性指定部署Catalog服務(wù)的URL 。對于此測試,它設(shè)置為目錄。
下一個重點是Hoverfly類規(guī)則部分。在該規(guī)則中,指定了以下內(nèi)容:
在測試之前啟動HTTP代理,并將來自JVM的所有傳出流量重定向到該代理。 它記錄了當(dāng)完成對主機(jī)目錄的請求并且路徑是 /api/products時,它必須返回給定json文檔的成功結(jié)果。
測試本身只使用TestRestTemplate(它是一個休息客戶端)并驗證您可以向購物車添加一些元素。
請注意,您無需配置啟動HTTP代理的位置或配置任何端口,因為Hoverfly會自動配置JVM網(wǎng)絡(luò)參數(shù),以便任何網(wǎng)絡(luò)通信都通過Hoverfly代理。
請注意,現(xiàn)在您不需要知道如何啟動Catalog服務(wù),也不需要知道如何使用正確的數(shù)據(jù)對其進(jìn)行配置。
您正在其邊界內(nèi)測試整個服務(wù),從傳入消息到傳出消息到其他服務(wù),而不模擬任何內(nèi)部元素。
您可能想知道“如果當(dāng)前服務(wù)還依賴于數(shù)據(jù)庫服務(wù)器會發(fā)生什么?”
在這種情況下,您什么也不做,因為服務(wù)本身知道正在使用哪個數(shù)據(jù)庫服務(wù)器以及它需要的數(shù)據(jù)類型,您只需要啟動數(shù)據(jù)庫服務(wù)器,填充所需的數(shù)據(jù)(夾具)并執(zhí)行測試。對于這種情況,我建議您使用Arquillian Cube Docker從Docker容器啟動數(shù)據(jù)庫服務(wù),這樣您就不需要在需要運(yùn)行測試的每臺機(jī)器上安裝它,而Arquillian Persistence Extension則用于將數(shù)據(jù)庫維護(hù)到已知狀態(tài)。
在下一個評級服務(wù)示例中,您可以簡要了解如何將它們用于持久性測試:
public?class?ApueCubeRatingServiceTest?{
??//?Starts?in?local?dockerhost?(docker?machine?or?native)?mongo?docker?image?before?running?the?test?class
??@ClassRule
??public?static?ContainerDslRule?mongodbContainer?=?new?ContainerDslRule("mongo:3.2.18-jessie")
??????.withPortBinding(27017);
??//Defines?APE?(Arquillian?Persistence?Extension?to?work?as?rule)
??@Rule
??public?ArquillianPersistenceRule?arquillianPersistenceRule?=?new?ArquillianPersistenceRule();
??//?Defines?to?use?MongoDb?as?NoSql?Populator
??@MongoDb
??@ArquillianResource
??NoSqlPopulator?populator;
??@Test
??public?void?should_calculate_average_rating_when_adding_an_already_inserted_item()?{
????createPopulatorConfiguration()
????????????????.usingDataSet("single_rating_with_double.json")
????????????????.execute();
????//?Execute?test
??}
??@After
??public?void?tearDown()?{
????createPopulatorConfiguration().clean();
??}
??private?NoSqlPopulatorConfigurator?createPopulatorConfiguration()?{
??????????return?populator.forServer(
??????????????mongodbContainer.getIpAddress(),
??????????????mongodbContainer.getBindPort(27017))
??????????????.withStorage(TEST_DATABASE);
??}
}
通過這種方法,您可以確保服務(wù)的所有內(nèi)部組件按預(yù)期工作,并避免微服務(wù)中端到端測試的片狀性質(zhì)。
因此,任何微服務(wù)中的端到端測試與整體應(yīng)用程序中的端到端測試并不完全相同; 您仍在測試整個服務(wù),但保持受控環(huán)境,其中測試僅依賴于服務(wù)邊界內(nèi)的組件。
合同測試如何適應(yīng)?那么,這里顯示的所有內(nèi)容都可以用于合同測試的消費(fèi)者和提供者方面,以避免啟動任何外部服務(wù)。通過這種方式,正如許多作者所總結(jié)的那樣,如果您使用合同測試,這些將成為新的端到端測試。
原文標(biāo)題《Writing End to End Tests for a Microservices Architecture》
作者:Alex Soto
譯者:February
