Spring Cloud Contract 微服務契約測試

簡介

使用場景

主要用於在微服務架構下作CDC(消費者驅動契約)測試。下圖展現了多個微服務的調用,若是咱們更改了一個模塊要如何進行測試呢?java

  • 傳統的兩種測試思路
    • 模擬生產環境部署全部的微服務,而後進行測試
      • 優勢
        • 測試結果可信度高
      • 缺點
        • 測試成本太大,裝一整套環境耗時,耗力,耗機器
    • Mock其餘微服務作端到端的測試
      • 優勢
        • 不用裝整套產品了,測的也方便快捷
      • 缺點
        • 須要寫不少服務的Mock,要維護一大堆不一樣版本用途的simulate(模擬器),一樣耗時耗力
  • Spring Cloud Contrct解決思路
    • 每一個服務都生產可被驗證的 Stub Runner,經過WireMock調用,服務雙方簽定契約,一方變化就更新本身的Stub,而且測對方的Stub。Stub其實只提供了數據,也就是契約,能夠很輕量的模擬服務的請求返回。而Mock可在Stub的基礎上增長驗證

契約測試流程

  • 服務提供者
    • 編寫契約,能夠用Groovy DSL 腳本也能夠用 YAML文件
    • 編寫測試基類用於構建過程當中插件自動生成測試用例
    • 生成的測試用例會自動運行,這時若是我麼提供的服務不能知足契約中的規則就會失敗
    • 提供者不斷完善功能直到服務知足契約要求
    • 發佈Jar包,同時將Stub後綴的jar一同發佈
  • 服務消費者
    • 對須要依賴外部服務的接口編寫測試用例
    • 經過註解指定須要依賴服務的Stub jar包
    • 驗證外部服務沒有問題

簡單案例

服務提供者

模擬一個股票價格查詢的服務git

項目地址

springcloud-contract-provider-restgithub

項目結構

項目依賴

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-contract-verifier</artifactId>
  <scope>test</scope>
</dependency>

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-contract-maven-plugin</artifactId>
      <version>2.2.1.RELEASE</version>
      <extensions>true</extensions>
      <configuration>
        <!--用於構建過程當中插件自動生成測試用例的基類-->
        <baseClassForTests>
          com.example.springcloudcontractproviderrest.RestBaseCase
        </baseClassForTests>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>

編寫契約

既然是消費者驅動契約,我麼首先須要制定契約,這裏爲了方便假設查詢貴州茅臺的股價返回值是固定的999,也能夠經過正則等方式去限制返回值spring

Contract.make {
    description "query by id should return stock(id,price)"

    request {
        method GET()
        url value {
            // 消費者使用時請求任何 /stock/price/數字 都會被轉爲 /stock/price/600519
            consumer regex('/stock/price/\\d+')
            producer "/stock/price/600519"
        }
    }

    response {
        status OK()
        headers {
            contentType applicationJson()
        }
        // 提供給消費者的默認返回
        body([
                id   : 600519,
                price: 999
        ])

        // 服務端在測試過程當中,body須要知足的規則
        bodyMatchers {
            jsonPath '$.id', byRegex(number())
            jsonPath '$.price', byRegex(number())
        }
    }
}

測試基類

主要是加載環境,而後因爲不是真實環境模擬了數據庫查詢數據庫

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

    @Autowired
    private StockController stockController;

    @MockBean
    private StockRepository stockRepository;

    @Before
    public void setup() {
        init();
        RestAssuredMockMvc.standaloneSetup(stockController);
    }

    private void init() {
        Mockito.when(stockRepository.getStockById(600519)).thenReturn(new StockDTO(600519, "貴州茅臺", 999L, "SH"));
    }

}

實現服務並測試

實現咱們的服務功能,具體代碼邏輯能夠在項目地址中查看,而後測試看是否符合契約json

mvn clean test架構

能夠在生成(target)目錄中找到 generated-test-sources 這個目錄,插件爲咱們自動生成而且運行的case就在其中app

public class StockTest extends RestBaseCase {

    @Test
    public void validate_shoudReturnStockIdAndPrice() throws Exception {
        // given:
            MockMvcRequestSpecification request = given();


        // when:
            ResponseOptions response = given().spec(request)
                    .get("/stock/price/600519");

        // then:
            assertThat(response.statusCode()).isEqualTo(200);
            assertThat(response.header("Content-Type")).matches("application/json.*");

        // and:
            DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());

        // and:
            assertThat(parsedJson.read("$.id", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
            assertThat(parsedJson.read("$.price", String.class)).matches("-?(\\d*\\.\\d+|\\d+)");
    }

}

發佈

若是一切順利就能夠deploy了maven

服務消費者

模擬查詢我的資產的服務,須要遠程調用股票價格查詢服務,計算總資產ide

項目地址

springcloud-contract-consumer-rest

項目結構

驗證服務

編寫測試用例驗證服務

@SpringBootTest
@RunWith(SpringRunner.class)
@AutoConfigureStubRunner(
        ids = {"com.example:springcloud-contract-provider-rest:+:stubs:8880"},
        stubsMode = StubRunnerProperties.StubsMode.LOCAL
)
public class StockApiTest {

    @Autowired
    private StockApi stockApi;

    @Test
    public void testStockApi() throws IOException {
        StockPriceDTO stockPrice = stockApi.getStockPrice(600519).execute().body();
        BDDAssertions.then(stockPrice.getId()).isEqualTo(600519);
        BDDAssertions.then(stockPrice.getPrice()).isEqualTo(999);

    }
}
相關文章
相關標籤/搜索