真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

SpringBoot單元測試和集成測試實現(xiàn)詳解

學(xué)習(xí)如何使用本教程中提供的工具,并在 Spring Boot 環(huán)境中編寫單元測試和集成測試。

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供景洪網(wǎng)站建設(shè)、景洪做網(wǎng)站、景洪網(wǎng)站設(shè)計、景洪網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計與制作、景洪企業(yè)網(wǎng)站模板建站服務(wù),十載景洪做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。

1. 概覽

本文中,我們將了解如何編寫單元測試并將其集成在 Spring Boot 環(huán)境中。你可在網(wǎng)上找到大量關(guān)于這個主題的教程,但很難在一個頁面中找到你需要的所有信息。我經(jīng)常注意到初級開發(fā)人員混淆了單元測試和集成測試的概念,特別是在談到 Spring 生態(tài)系統(tǒng)時。我將嘗試講清楚不同注解在不同上下文中的用法。

2. 單元測試 vs. 集成測試

維基百科是這么說單元測試的:

在計算機編程中,單元測試是一種軟件測試方法,用以測試源代碼的單個單元、一個或多個計算機程序模塊的集合以及相關(guān)的控制數(shù)據(jù)、使用過程和操作過程,以確定它們是否適合使用。

集成測試:

“集成測試(有時也稱集成和測試,縮寫為 I&T)是軟件測試的一個階段,在這個階段中,各個軟件模塊被組合在一起來進行測試?!?/p>

簡而言之,當(dāng)我們在做單元測試時,只是測試了一個代碼單元,每次只測試一個方法,不包括與正測試組件相交互的其他所有組件。

另一方面,在集成測試中,我們測試各組件之間的集成。由于單元測試,我們可知這些組件行為與所需一致,但不清楚它們是如何在一起工作的。這就是集成測試的職責(zé)。

3. Java 單元測試

所有 Java 開發(fā)者都知道 JUnit 是執(zhí)行單元測試的主要框架。它提供了許多注解來對期望進行斷言。

Hamcrest 是一個用于軟件測試的附加框架。Hamcrest 允許使用現(xiàn)有的 matcher 類來檢查代碼中的條件,還允許自定義 matcher 實現(xiàn)。要在 JUnit 中使用 Hamcrest matcher,必須使用 assertThat 語句,后跟一個或多個 matcher。

在這里,你可以看到使用這兩種框架的簡單測試:

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;

import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;

public class AssertTests {
 @Test
 public void testAssertArrayEquals() {
  byte[] expected = "trial".getBytes();
  byte[] actual = "trial".getBytes();
  assertArrayEquals("failure - byte arrays not same", expected, actual);
 }

 @Test
 public void testAssertEquals() {
  assertEquals("failure - strings are not equal", "text", "text");
 }

 @Test
 public void testAssertFalse() {
  assertFalse("failure - should be false", false);
 }

 @Test
 public void testAssertNotNull() {
  assertNotNull("should not be null", new Object());
 }

 @Test
 public void testAssertNotSame() {
  assertNotSame("should not be same Object", new Object(), new Object());
 }

 @Test
 public void testAssertNull() {
  assertNull("should be null", null);
 }

 @Test
 public void testAssertSame() {
  Integer aNumber = Integer.valueOf(768);
  assertSame("should be same", aNumber, aNumber);
 }

 // JUnit Matchers assertThat
 @Test
 public void testAssertThatBothContainsString() {
  assertThat("albumen", both(containsString("a")).and(containsString("b")));
 }

 @Test
 public void testAssertThatHasItems() {
  assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
 }

 @Test
 public void testAssertThatEveryItemContainsString() {
  assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
 }

 // Core Hamcrest Matchers with assertThat
 @Test
 public void testAssertThatHamcrestCoreMatchers() {
  assertThat("good", allOf(equalTo("good"), startsWith("good")));
  assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
  assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
  assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
  assertThat(new Object(), not(sameInstance(new Object())));
 }

 @Test
 public void testAssertTrue() {
  assertTrue("failure - should be true", true);
 }
}

4. 介紹我們的案例

讓我們來寫一個簡單的程序吧。其目的是為漫畫提供一個基本的搜索引擎。

4.1. Maven 依賴

首先,需要添加一些依賴到我們的工程中。


 org.springframework.boot
 spring-boot-starter-test
 test


 org.springframework.boot
 spring-boot-starter-web


 org.projectlombok
 lombok
 1.16.20
 provided

4.2. 定義 Model

我們的模型非常簡單,只有兩個類組成:Manga 和 MangaResult

4.2.1. Manga 類

Manga 類表示系統(tǒng)檢索到的 Manga 實例。使用 Lombok 來減少樣板代碼。

package com.mgiglione.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
public class Manga {
  private String title;
  private String description;
  private Integer volumes;
  private Double score;
}

4.2.2. MangaResult

MangaResult 類是包含了一個 Manga List 的包裝類。

package com.mgiglione.model;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter @Setter @NoArgsConstructor
public class MangaResult {
  private List result;
}

4.3. 實現(xiàn) Service

為實現(xiàn)本 Service,我們將使用由 Jikan Moe 提供的免費 API 接口。

RestTemplate 是用來對 API 進行發(fā)起 REST 調(diào)用的 Spring 類。

package com.mgiglione.service;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.mgiglione.model.Manga;
import com.mgiglione.model.MangaResult;

@Service
public class MangaService {

  Logger logger = LoggerFactory.getLogger(MangaService.class);
  private static final String MANGA_SEARCH_URL="http://api.jikan.moe/search/manga/";
  
  @Autowired
  RestTemplate restTemplate;
  
  public List getMangasByTitle(String title) {
    return restTemplate.getForEntity(MANGA_SEARCH_URL+title, MangaResult.class).getBody().getResult();
  }
}

4.4. 實現(xiàn) Controller

下一步就是寫一個暴露了兩個端點的 REST Controller,一個是同步的,一個是異步的,其僅用于測試目的。該 Controller 使用了上面定義的 Service。

package com.mgiglione.controller;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;

@RestController
@RequestMapping(value = "/manga")
public class MangaController {

  Logger logger = LoggerFactory.getLogger(MangaController.class);
  
  @Autowired
  private MangaService mangaService;  
  
  @RequestMapping(value = "/async/{title}", method = RequestMethod.GET)
  @Async
  public CompletableFuture> searchASync(@PathVariable(name = "title") String title) {
    return CompletableFuture.completedFuture(mangaService.getMangasByTitle(title));
  }
  
  @RequestMapping(value = "/sync/{title}", method = RequestMethod.GET)
  public @ResponseBody > searchSync(@PathVariable(name = "title") String title) {
    return mangaService.getMangasByTitle(title);
  }
}

4.5. 啟動并測試系統(tǒng)

mvn spring-boot:run

然后,Let's try it:

curl http://localhost:8080/manga/async/ken
curl http://localhost:8080/manga/sync/ken

示例輸出:

{ 
  "title":"Rurouni Kenshin: Meiji Kenkaku Romantan",
  "description":"Ten years have passed since the end of Bakumatsu, an era of war that saw the uprising of citizens against the Tokugawa shogunate. The revolutionaries wanted to create a time of peace, and a thriving c...",
  "volumes":28,
  "score":8.69
},
{ 
  "title":"Sun-Ken Rock",
  "description":"The story revolves around Ken, a man from an upper-class family that was orphaned young due to his family's involvement with the Yakuza; he became a high school delinquent known for fighting. The only...",
  "volumes":25,
  "score":8.12
},
{ 
  "title":"Yumekui Kenbun",
  "description":"For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....",
  "volumes":9,
  "score":7.97
}

5. Spring Boot 應(yīng)用的單元測試

Spring Boot 提供了一個強大的類以使測試變得簡單: @SpringBootTest 注解

可以在基于 Spring Boot 運行的測試類上指定此注解。

除常規(guī) Spring TestContext Framework 之外,其還提供以下功能:

  • 當(dāng) @ContextConfiguration (loader=…) 沒有特別聲明時,使用 SpringBootContextLoader 作為默認(rèn) ContextLoader。
  • 在未使用嵌套的 @Configuration 注解,且未顯式指定相關(guān)類時,自動搜索 @SpringBootConfiguration。
  • 允許使用 Properties 來自定義 Environment 屬性。
  • 對不同的 Web 環(huán)境模式提供支持,包括啟動在已定義或隨機端口上的完全運行的 Web 服務(wù)器的功能。
  • 注冊 TestRestTemplate 和 / 或 WebTestClient Bean,以便在完全運行在 Web 服務(wù)器上的 Web 測試中使用。

此處,我們僅有兩個組件需要測試:MangaService 和 MangaController

5.1. 對 MangaService 進行單元測試

為了測試 MangaService,我們需要將其與外部組件隔離開來。本例中,只需要一個外部組件:RestTemplate,我們用它來調(diào)用遠(yuǎn)程 API。

我們需要做的是模擬 RestTemplate Bean,并讓它始終以固定的給定響應(yīng)進行響應(yīng)。Spring Test 結(jié)合并擴展了 Mockito 庫,通過 @MockBean 注解,我們可以配置模擬 Bean。

package com.mgiglione.service.test.unit;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;

import com.mgiglione.model.Manga;
import com.mgiglione.model.MangaResult;
import com.mgiglione.service.MangaService;
import com.mgiglione.utils.JsonUtils;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MangaServiceUnitTest {
  
  @Autowired
  private MangaService mangaService;
  
  // MockBean is the annotation provided by Spring that wraps mockito one
  // Annotation that can be used to add mocks to a Spring ApplicationContext.
  // If any existing single bean of the same type defined in the context will be replaced by the mock, if no existing bean is defined a new one will be added.
  @MockBean
  private RestTemplate template;
  
  @Test
  public void testGetMangasByTitle() throws IOException {
    // Parsing mock file
    MangaResult mRs = JsonUtils.jsonFile2Object("ken.json", MangaResult.class);
    // Mocking remote service
    when(template.getForEntity(any(String.class), any(Class.class))).thenReturn(new ResponseEntity(mRs, HttpStatus.OK));
    // I search for goku but system will use mocked response containing only ken, so I can check that mock is used.
    List mangasByTitle = mangaService.getMangasByTitle("goku");
    assertThat(mangasByTitle).isNotNull()
      .isNotEmpty()
      .allMatch(p -> p.getTitle()
        .toLowerCase()
        .contains("ken"));
  }
  
}

5.2. 對 MangaController 進行單元測試

正如在 MangaService 的單元測試中所做的那樣,我們需要隔離組件。在這種情況下,我們需要模擬 MangaService Bean。

然后,我們還有一個問題……Controller 部分是管理 HttpRequest 的系統(tǒng)的一部分,因此我們需要一個系統(tǒng)來模擬這種行為,而非啟動完整的 HTTP 服務(wù)器。

MockMvc 是執(zhí)行該操作的 Spring 類。其可以以不同的方式進行設(shè)置:

  • 使用 Standalone Context
  • 使用 WebApplication Context
  • 讓 Spring 通過在測試類上使用 @SpringBootTest、@AutoConfigureMockMvc 這些注解來加載所有的上下文,以實現(xiàn)自動裝配
  • 讓 Spring 通過在測試類上使用 @WebMvcTest 注解來加載 Web 層上下文,以實現(xiàn)自動裝配
package com.mgiglione.service.test.unit;

import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

import java.util.ArrayList;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;

import com.mgiglione.controller.MangaController;
import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MangaControllerUnitTest {

  MockMvc mockMvc;
  
  @Autowired
  protected WebApplicationContext wac;
  
  @Autowired
  MangaController mangaController;
  
  @MockBean
  MangaService mangaService;
  
  /**
   * List of samples mangas
   */
  private List mangas;
  
  @Before
  public void setup() throws Exception {
    this.mockMvc = standaloneSetup(this.mangaController).build();// Standalone context
    // mockMvc = MockMvcBuilders.webAppContextSetup(wac)
    // .build();
    Manga manga1 = Manga.builder()
      .title("Hokuto no ken")
      .description("The year is 199X. The Earth has been devastated by nuclear war...")
      .build();
    Manga manga2 = Manga.builder()
      .title("Yumekui Kenbun")
      .description("For those who suffer nightmares, help awaits at the Ginseikan Tea House, where patrons can order much more than just Darjeeling. Hiruko is a special kind of a private investigator. He's a dream eater....")
      .build();
    mangas = new ArrayList<>();
    mangas.add(manga1);
    mangas.add(manga2);
  }
  
  @Test
  public void testSearchSync() throws Exception {
    // Mocking service
    when(mangaService.getMangasByTitle(any(String.class))).thenReturn(mangas);
    mockMvc.perform(get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].title", is("Hokuto no ken")))
      .andExpect(jsonPath("$[1].title", is("Yumekui Kenbun")));
  }

  @Test
  public void testSearchASync() throws Exception {
    // Mocking service
    when(mangaService.getMangasByTitle(any(String.class))).thenReturn(mangas);
    MvcResult result = mockMvc.perform(get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpect(request().asyncStarted())
      .andDo(print())
      // .andExpect(status().is2xxSuccessful()).andReturn();
      .andReturn();
    // result.getRequest().getAsyncContext().setTimeout(10000);
    mockMvc.perform(asyncDispatch(result))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(jsonPath("$[0].title", is("Hokuto no ken")));
  }
}

正如在代碼中所看到的那樣,選擇第一種解決方案是因為其是最輕量的一個,并且我們可以對 Spring 上下文中加載的對象有更好的治理。

在異步測試中,必須首先通過調(diào)用服務(wù),然后啟動 asyncDispatch 方法來模擬異步行為。

6. Spring Boot 應(yīng)用的集成測試

對于集成測試,我們希望提供下游通信來檢查我們的主要組件。

6.1. 對 MangaService 進行集成測試

這個測試也是非常簡單的。我們不需要模擬任何東西,因為我們的目的就是要調(diào)用遠(yuǎn)程 Manga API。

package com.mgiglione.service.test.integration;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.mgiglione.model.Manga;
import com.mgiglione.service.MangaService;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MangaServiceIntegrationTest {

  @Autowired
  private MangaService mangaService;
  
  @Test
  public void testGetMangasByTitle() {
      List mangasByTitle = mangaService.getMangasByTitle("ken");
      assertThat(mangasByTitle).isNotNull().isNotEmpty();
  }
}

6.2. 對 MangaController 進行集成測試

這個測試和單元測試很是相似,但在這個案例中,我們無需再模擬 MangaService。

package com.mgiglione.service.test.integration;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.web.context.WebApplicationContext;

import com.mgiglione.controller.MangaController;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MangaControllerIntegrationTest {

  // @Autowired
  MockMvc mockMvc;
  
  @Autowired
  protected WebApplicationContext wac;
  
  @Autowired
  MangaController mangaController;
  
  @Before
  public void setup() throws Exception {
    this.mockMvc = standaloneSetup(this.mangaController).build();// Standalone context
    // mockMvc = MockMvcBuilders.webAppContextSetup(wac)
    // .build();
  }
  
  @Test
  public void testSearchSync() throws Exception {
    mockMvc.perform(get("/manga/sync/ken").contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
  }
  
  @Test
  public void testSearchASync() throws Exception {
    MvcResult result = mockMvc.perform(get("/manga/async/ken").contentType(MediaType.APPLICATION_JSON))
      .andDo(print())
      .andExpect(request().asyncStarted())
      .andDo(print())
      .andReturn();
    mockMvc.perform(asyncDispatch(result))
      .andDo(print())
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.*.title", hasItem(is("Hokuto no Ken"))));
  }
}

7. 結(jié)論

我們已經(jīng)了解了在 Spring Boot 環(huán)境下單元測試和集成測試的主要不同,了解了像 Hamcrest 這樣簡化測試編寫的框架。當(dāng)然,也可以在我的 GitHub 倉庫 里找到所有代碼。

原文:https://dzone.com/articles/unit-and-integration-tests-in-spring-boot-2

作者:Marco Giglione

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。


文章題目:SpringBoot單元測試和集成測試實現(xiàn)詳解
轉(zhuǎn)載來源:http://weahome.cn/article/jsihjj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部