iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 28
0

消費者驅動的契約測試(Consumer-Driven Contracts,簡稱CDC),是指從消費者業務實現的角度出發,驅動出契約,再基於契約,對提供者驗證的一種測試方式。

原本你要測試的話必須啟動相依的服務

透過 Spring Cloud Contract 的實踐之後你不用啟動這麼多服務,只需拿 Stub 來提供測試

簡單說就是先訂好消費者(consumer)要什麼樣的格式與範例資料,再基於這份契約開發生產者(producer),打包時,除了正常版本的應用程式,還會額外產生 Stub 的 jar ,這個 Stub 也是基於當初的契約來產生能夠透過 Http 回覆簡易資料的啟動器

producer 生產者

來看一下 生產者(producer) 的 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>customer-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Dalston.SR4</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-contract-verifier</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-contract-maven-plugin</artifactId>
				<version>1.1.4.RELEASE</version>
				<extensions>true</extensions>
				<configuration>
					<baseClassForTests>com.example.demo.TestContract</baseClassForTests>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

比較要提的是要加入 spring-cloud-starter-contract-verifier 來幫你驗證是否符合契約部分
以及另外自己要加入 spring-cloud-contract-maven-plugin,baseClassForTests 這個就是你要符合契約的那支測試程式

看一下專案架構

首先來看一下所謂生產跟消費間的契約是什麼東西

import org.springframework.cloud.contract.spec.Contract
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType

Contract.make {
    description "return all customers"

    request {
        url "/api/customers"
        method GET()
    }

    response {
        status 200
        headers {
            header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
        }
        body("data": [[id: 1L, name: "sam"], [id: 2L, name: "andy"]])
        //body("""{"data":[{"id":1,"name":"sam"},{"id":2,"name":"andy"}]}""")
    }
}

契約 採用 groovy 的 DSL 描述,所以非常好閱讀
可以得知 需要透過 /api/customers 取得一個 json 裡面 data 是一個 list

接下來我們就來實現這份契約

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private Long id;
    private String name;
}
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
@Data
public class Page {
    private Collection<Customer> data;
}
@RestController
public class CustomerRestController {

    @Autowired
    private CustomerRepository customerRepository;

    @RequestMapping(path = "/api/customers")
    public Page getCustomers() {
        Page page = new Page();
        page.setData(customerRepository.findAll());
        return page;
    }
}

好了,基本上我們已經實踐契約的內容,接下來透過 verifier 驗證跟產生 Stub ,
也就是透過這支測試程式 com.example.demo.TestContract

import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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.test.context.junit4.SpringRunner;
import java.util.Arrays;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class TestContract {

    @Autowired
    private CustomerRestController customerRestController;
    @MockBean
    private CustomerRepository customerRepository;

    @Before
    public void before() {
        Mockito.when(customerRepository.findAll()).thenReturn(
                Arrays.asList(new Customer(1L, "sam"), new Customer(2L, "andy")));

        RestAssuredMockMvc.standaloneSetup(this.customerRestController);
    }
}

然後執行 maven clear install 測試完後就會裝在本機的 Maven 庫
也可以看到產生了兩個 jar
customer-service-0.0.1-SNAPSHOT.jar
customer-service-0.0.1-SNAPSHOT-stubs.jar

想測試 stub.jar 的話可以看 Stub Runner Boot
抓這個 https://dl.bintray.com/marcingrzejszczak/maven/stub-runner-boot-1.1.0.RELEASE.jar

啟動 stub

java -jar stub-runner-boot-1.1.0.RELEASE.jar --stubrunner.ids=com.example:customer-service:+:8866 --stubrunner.workOffline=true

之後你就可以從 http://localhost:8866/api/customers 取得跟契約一致的資料


上一篇
Day 27 - 斷路器多服務集中監控 Turbine
下一篇
Day 29 - 服務合約模擬器 Cloud Contract Stub Runner
系列文
30天從零開始 使用 Spring Boot 跟 Spring Cloud 建構完整微服務架構35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言