原創(chuàng)|使用教程|編輯:鄭恭琳|2021-01-22 09:59:11.580|閱讀 204 次
概述:Spring框架(以及Spring Boot)提供了一個(gè)有用的測(cè)試框架,可為您的Spring Controllers編寫JUnit測(cè)試。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
Spring框架(以及Spring Boot)提供了一個(gè)有用的測(cè)試框架,可為您的Spring Controllers編寫JUnit測(cè)試。
在我以前的文章中,我們討論了如何使用Parasoft Jtest的Unit Test Assistant有效地構(gòu)建和改進(jìn)這些測(cè)試。在本文中,我將繼續(xù)解決測(cè)試任何復(fù)雜應(yīng)用程序的最大挑戰(zhàn)之一:依賴關(guān)系管理。
說實(shí)話。復(fù)雜的應(yīng)用程序不是從頭開始構(gòu)建的,而是使用庫(kù),API以及由其他人構(gòu)建和維護(hù)的核心項(xiàng)目或服務(wù)。作為Spring開發(fā)人員,我們盡可能地利用現(xiàn)有功能,因此我們可以將時(shí)間和精力花在我們關(guān)心的方面:應(yīng)用程序的業(yè)務(wù)邏輯。我們將詳細(xì)信息留給庫(kù),因此我們的應(yīng)用程序具有很多依賴性,如下面的橙色所示:
圖1.具有多個(gè)依賴項(xiàng)的Spring服務(wù)
如果單元測(cè)試的大多數(shù)功能取決于這些依賴項(xiàng)的行為,那么如何將單元測(cè)試集中在應(yīng)用程序(控制器和服務(wù))上?最后,我不是總是執(zhí)行集成測(cè)試而不是單元測(cè)試嗎?如果我需要更好地控制這些依賴項(xiàng)的行為,或者在單元測(cè)試期間這些依賴項(xiàng)不可用,該怎么辦?
我需要的是一種將應(yīng)用程序與這些依賴項(xiàng)隔離的方法,因此我可以將單元測(cè)試的重點(diǎn)放在應(yīng)用程序代碼上。在某些情況下,我們可以為這些依賴項(xiàng)創(chuàng)建專門的“測(cè)試”版本。但是,由于多種原因,使用Mockito之類的標(biāo)準(zhǔn)化庫(kù)相對(duì)于此方法有很多好處:
圖2.模擬服務(wù)替換了多個(gè)依賴關(guān)系
Spring的依賴
通常,Spring應(yīng)用程序?qū)⒐δ懿鸱譃?/span>Bean。控制器可能取決于Service Bean,而Service Bean可能取決于EntityManager,JDBC連接或另一個(gè)Bean。在大多數(shù)情況下,需要隔離測(cè)試代碼的依賴是bean。在集成測(cè)試中,所有層都應(yīng)該是真實(shí)的,但是對(duì)于單元測(cè)試,我們需要確定哪些依賴項(xiàng)應(yīng)該是真實(shí)的,哪些應(yīng)該是模擬的。
Spring允許開發(fā)人員使用XML,Java或兩者的結(jié)合來定義和配置bean,以在您的配置中混合使用模擬bean和實(shí)際bean。由于需要在Java中定義模擬對(duì)象,因此應(yīng)使用Configuration類來定義和配置模擬的bean。
當(dāng)UTA生成Spring測(cè)試時(shí),控制器的所有依賴項(xiàng)都將設(shè)置為模擬,以便每個(gè)測(cè)試都可以控制該依賴項(xiàng)。運(yùn)行測(cè)試時(shí),UTA會(huì)檢測(cè)尚未配置方法模擬的方法在模擬對(duì)象上進(jìn)行的方法調(diào)用,并建議應(yīng)模擬那些方法。然后,我們可以使用快速修復(fù)程序自動(dòng)模擬每種方法。
這是依賴于PersonService的示例控制器:
@Controller @RequestMapping("/people") public class PeopleController { @Autowired protected PersonService personService; @GetMapping public ModelAndView people(Model model){ for (Person person : personService.getAllPeople()) { model.addAttribute(person.getName(), person.getAge()); } return new ModelAndView("people.jsp", model.asMap()); } }
還有由Parasoft Jtest的單元測(cè)試助手生成的示例測(cè)試:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class PeopleControllerTest { @Autowired PersonService personService; // Other fields and setup @Configuration static class Config { // Other beans @Bean public PersonService getPersonService() { return mock(PersonService.class); } } @Test public void testPeople() throws Exception { // When ResultActions actions = mockMvc.perform(get("/people")); } }
在這里,測(cè)試使用帶有@Configuration注釋的內(nèi)部類,該類使用Java配置為受測(cè)控制器提供Bean依賴關(guān)系。這使我們可以在bean方法中模擬PersonService。還沒有模擬方法,因此在運(yùn)行測(cè)試時(shí),我看到以下建議:
這意味著在我模擬的PersonService上調(diào)用了getAllPeople()方法,但是測(cè)試尚未為此方法配置模擬。當(dāng)我選擇“模擬”快速修復(fù)選項(xiàng)時(shí),測(cè)試將更新:
@Test public void testPeople() throws Exception { Collection<Person> getAllPeopleResult = new ArrayList<Person>(); doReturn(getAllPeopleResult).when(personService).getAllPeople(); // When ResultActions actions = mockMvc.perform(get("/people"));
當(dāng)我再次運(yùn)行測(cè)試時(shí),它通過了。我仍然應(yīng)該填充由getAllPeople()返回的Collection,但是解決了設(shè)置我的模擬依賴項(xiàng)的挑戰(zhàn)。
請(qǐng)注意,我可以將生成的方法模擬從測(cè)試方法移到Configuration類的bean方法中。如果這樣做,則意味著該類中的每個(gè)測(cè)試都將以相同的方式模擬相同的方法。將方法模擬保留在測(cè)試方法中意味著可以在不同的測(cè)試之間對(duì)方法進(jìn)行不同的模擬。
Spring Boot
Spring Boot使Bean模擬變得更加容易。無需為測(cè)試中的bean和定義它的Configuration類使用@Autowired字段,您只需使用bean的字段并使用@MockBean對(duì)其進(jìn)行注釋即可。 Spring Boot將使用它在類路徑上找到的模擬框架為該bean創(chuàng)建一個(gè)模擬,并以可以注入容器中任何其他bean的方式注入它。當(dāng)使用單元測(cè)試助手生成Spring Boot測(cè)試時(shí),使用@MockBean功能而不是Configuration類。
@SpringBootTest @AutoConfigureMockMvc public class PeopleControllerTest { // Other fields and setup – no Configuration class needed! @MockBean PersonService personService; @Test public void testPeople() throws Exception { ... } }
XML與Java配置
在上面的第一個(gè)示例中,Configuration類將所有bean提供給Spring容器。另外,您可以將XML配置用于測(cè)試,而不是Configuration類。或者您可以將兩者結(jié)合起來。例如:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({ "classpath:/**/testContext.xml" }) public class PeopleControllerTest { @Autowired PersonService personService; // Other fields and setup @Configuration static class Config { @Bean @Primary public PersonService getPersonService() { return mock(PersonService.class); } } // Tests }
在這里,該類在@ContextConfiguration批注(此處未顯示)中引用XML配置文件以提供大多數(shù)bean,這些bean可以是真實(shí)bean或特定于測(cè)試的bean。 我們還提供了@Configuration類,其中模擬了PersonService。@Primary批注指示即使在XML配置中找到了PersonService bean,該測(cè)試也將使用@Configuration類中的模擬bean。這種類型的配置可以使測(cè)試代碼更小、更易于管理。
您可以使用所需的任何特定@ContextConfiguration屬性將UTA配置為生成測(cè)試。
模擬靜態(tài)方法
有時(shí),依賴項(xiàng)是靜態(tài)訪問的。例如,應(yīng)用程序可能通過靜態(tài)方法調(diào)用訪問第三方服務(wù):
public class ExternalPersonService { public static Person getPerson(int id) { RestTemplate restTemplate = new RestTemplate(); try { return restTemplate.getForObject("http://domain.com/people/" + id, Person.class); } catch (RestClientException e) { return null; } } }
在我們的控制器中:
@GetMapping public ResponseEntity<Person> getPerson(@PathVariable("id") int id, Model model) { Person person = ExternalPersonService.getPerson(id); if (person != null) { return new ResponseEntity<Person>(person, HttpStatus.OK); } return new ResponseEntity<>(HttpStatus.NOT_FOUND); }
在此示例中,我們的處理程序方法使用靜態(tài)方法調(diào)用從第三方服務(wù)獲取Person對(duì)象。當(dāng)我們?yōu)榇颂幚沓绦蚍椒?gòu)建JUnit測(cè)試時(shí),每次運(yùn)行測(cè)試時(shí)都會(huì)對(duì)服務(wù)進(jìn)行真正的HTTP調(diào)用。
相反,讓我們模擬靜態(tài)的ExternalPersonService.getPerson()方法。這可以防止HTTP調(diào)用,并允許我們提供適合我們測(cè)試需求的Person對(duì)象響應(yīng)。單元測(cè)試助手可以使使用PowerMockito模擬靜態(tài)方法更加容易。
UTA為上面的處理程序方法生成一個(gè)測(cè)試,如下所示:
@Test public void testGetPerson() throws Throwable { // When long id = 1L; ResultActions actions = mockMvc.perform(get("/people/" + id)); // Then actions.andExpect(status().isOk()); }
運(yùn)行測(cè)試時(shí),我們將看到在UTA流樹中進(jìn)行的HTTP調(diào)用。讓我們找到對(duì)ExternalPersonService.getPerson()的調(diào)用,并對(duì)其進(jìn)行模擬:
測(cè)試已更新為使用PowerMock模擬此測(cè)試的靜態(tài)方法:
@Test public void testGetPerson() throws Throwable { spy(ExternalPersonService.class); Person getPersonResult = null; // UTA: default value doReturn(getPersonResult).when(ExternalPersonService.class, "getPerson", anyInt()); // When int id = 0; ResultActions actions = mockMvc.perform(get("/people/" + id)); // Then actions.andExpect(status().isOk()); }
現(xiàn)在,使用UTA,我們可以選擇getPersonResult變量并將其實(shí)例化,以便模擬方法調(diào)用不會(huì)返回null:
String name = ""; // UTA: default value int age = 0; // UTA: default value Person getPersonResult = new Person(name, age);
當(dāng)我們?cè)俅芜\(yùn)行測(cè)試時(shí),getPersonResult從模擬的ExternalPersonService.getPerson()方法返回,并且測(cè)試通過。
注意:從流樹中,您還可以為靜態(tài)方法調(diào)用選擇“添加可模擬方法模式”。這會(huì)將單元測(cè)試助手配置為在生成新測(cè)試時(shí)始終模擬那些靜態(tài)方法調(diào)用。
復(fù)雜的應(yīng)用程序通常具有功能依賴性,這會(huì)使開發(fā)人員對(duì)代碼進(jìn)行單元測(cè)試的能力復(fù)雜化并受到限制。使用Mockito這樣的模擬框架可以幫助開發(fā)人員將測(cè)試中的代碼與這些依賴項(xiàng)隔離開來,從而使他們能夠更快地編寫更好的單元測(cè)試。Parasoft Jtest單元測(cè)試助手通過配置新的測(cè)試以使用模擬,并在運(yùn)行時(shí)查找丟失的方法模擬并幫助開發(fā)人員為它們生成模擬,使依賴關(guān)系管理變得容易。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn