java單元/集成測試中使用Testcontainers

1.Testcontainers介紹:

Testcontainers是一個Java庫,它支援JUnit測試,提供公共資料庫、SeleniumWeb瀏覽器或任何可以在Docker容器中運行的輕量級、一次性實例。

測試容器使以下類型的測試更加容易:

數據訪問層集成測試:

使用MySQL,PostgreSQL或Oracle資料庫的容器化實例測試您的數據訪問層程式碼,但無需在開發人員的電腦上進行複雜的設置,並且測試將始終從已知的資料庫狀態開始,避免「垃圾」數據的干擾。也可以使用任何其他可以容器化的資料庫類型。

應用程式集成測試:

用於在具有相關性(例如資料庫,消息隊列或Web伺服器)的短期測試模式下運行應用程式。

UI /驗收測試:

使用與Selenium兼容的容器化Web瀏覽器進行自動化UI測試。每個測試都可以獲取瀏覽器的新實例,而無需擔心瀏覽器狀態,插件版本或瀏覽器自動升級。您將獲得每個測試會話或測試失敗的影片記錄。

更多:

可以簽出各種貢獻的模組,或使用 GenericContainer作為基礎創建自己的自定義容器類。


2.Testcontainers實踐示例:

Testcontainers提供了多種現成的與測試關聯的應用程式容器,如下圖:

image-20200407113553146

在本文中,將演示集成postgresql容器和mockserver容器的測試。

Testcontainers必要條件:

1.Docker

2.支援的JVM測試框架:JUnit4,JUnit5,spock…

2.1 集成postgresql測試

依賴:

<dependency>      <groupId>org.testcontainers</groupId>      <artifactId>testcontainers</artifactId>      <version>1.12.5</version>      <scope>test</scope>  </dependency>  <dependency>      <groupId>org.testcontainers</groupId>    	<!--指定資料庫名稱,mysql,mariadb等等-->      <artifactId>postgresql</artifactId>      <version>1.12.5</version>      <scope>test</scope>  </dependency>  

配置:

在項目的src/test/resources/application.yml文件中配置postgresql相關資訊

#將驅動程式設置為org.testcontainers.jdbc.ContainerDatabaseDriver,它是一個Testcontainers JDBC代理驅動程式。初始化數據源時,此驅動程式將負責啟動所需的Docker容器。  spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver    #將JDBC URL設置為JDBC:tc:<database image>:<version>:///以便Testcontainers知道要使用哪個資料庫。  #TC_INITSCRIPT=指定的資料庫初始化的腳本文件位置  spring.datasource.url=jdbc:tc:postgresql:9.6:///?TC_INITSCRIPT=file:src/main/resources/init_db.sql    #將方言明確設置為資料庫的方言實現,否則在啟動應用程式時會收到異常。當您在應用程式中使用JPA時(通過Spring Data JPA),此步驟是必需的  spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect  

測試示例:

為了在@DataJpaTest中使用TC,您需要確保使用了應用程式定義的(自動配置的)數據源。您可以通過使用@AutoConfigureTestDatabase注釋測試來輕鬆完成此操作,如下所示:

@RunWith(SpringJUnit4ClassRunner.class)  @DataJpaTest  @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)  public class OwnerRepositoryTests {        @Autowired      private OwnerRepository ownerRepository;        @Test      void findAllReturnsJohnDoe() { // as defined in tc-initscript.sql          var owners = ownerRepository.findAll();          assertThat(owners.size()).isOne();          assertThat(owners.get(0).getFirstName()).isEqualTo("John");          assertThat(owners.get(0).getLastName()).isEqualTo("Doe");      }  }  

以上測試將使用Testcontainers提供的postgresql容器進行測試,從而排除了外部環境對測試的干擾。

當需要用本地資料庫進行集成測試時,我們只要使用@SpringBootTest替換如上兩個註解即可:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)  @AutoConfigureMockMvc  public class OwnerResourceTests {        @Autowired      WebApplicationContext wac;        @Test      void findAllReturnsJohnDoe() throws Exception {          given()                 .webAppContextSetup(wac)          .when()                  .get("/owners")          .then()                  .status(HttpStatus.OK)                  .body(                          "_embedded.owners.firstName", containsInAnyOrder("John"),                          "_embedded.owners.lastName", containsInAnyOrder("Doe")                  );      }  }  

以上測試將使用真實運行環境的資料庫進行測試。


2.2 集成mockServer測試

Mock Server可用於通過將請求與用戶定義的期望進行匹配來模擬HTTP服務。

依賴:

				<dependency>              <groupId>org.testcontainers</groupId>              <artifactId>mockserver</artifactId>              <version>1.12.5</version>              <scope>test</scope>          </dependency>          <dependency>              <groupId>org.mock-server</groupId>              <artifactId>mockserver-netty</artifactId>              <version>5.5.4</version>          </dependency>          <dependency>              <groupId>org.mock-server</groupId>              <artifactId>mockserver-client-java</artifactId>              <version>5.5.4</version>          </dependency>  

測試示例:

//創建一個MockServer容器  @Rule  public MockServerContainer mockServer = new MockServerContainer();  

以及使用Java MockServerClient設置簡單的期望。

new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())                  .when(request()                          .withPath("/person")                          .withQueryStringParameter("name", "peter"))                  .respond(response()                          .withBody("Peter the person!"));  //...當一個get請求至'/person?name=peter' 時會返回 "Peter the person!"  

測試(使用restassured進行測試):

RestAssured.baseURI = "http://" + mockServer.getContainerIpAddress();  RestAssured.port = mockServer.getServerPort();  given().queryParam("name", "peter")                  .get("/person")                  .then()                  .statusCode(HttpStatus.OK.value())                  .body(is("Peter the person!"));  

完整程式碼如下:

@RunWith(SpringJUnit4ClassRunner.class)  public class OneTests {      @Rule      public MockServerContainer mockServer = new MockServerContainer();        @Test      public void v() {          RestAssured.baseURI = "http://" + mockServer.getContainerIpAddress();          RestAssured.port = mockServer.getServerPort();            new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())                  .when(request()                          .withPath("/person")                          .withQueryStringParameter("name", "peter"))                  .respond(response()                          .withBody("Peter the person!"));            given().queryParam("name", "peter")                  .get("/person")                  .then()                  .statusCode(HttpStatus.OK.value())                  .body(is("Peter the person!"));      }  }  

3.總結:

Testcontainers輕鬆的解決了集成測試時測試程式碼與本地組件耦合,從而出現各種意外失敗的問題(比如本地資料庫中存在臟數據影響到了集成測試,多個集成測試同時運行時相互干擾導致測試結果意外失敗)。筆者之前專門為集成測試準備了一套資料庫,使數據和其他環境隔離掉,但還是會遇到多個集成測試一起跑相互干擾的問題,Testcontainers輕鬆的解決了筆者的問題。


關注筆者公眾號,推送各類原創/優質技術文章 ⬇️