自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一文掌握契約測(cè)試

開發(fā) 前端
契約測(cè)試主要解決在存在溝通邊界情況下,測(cè)試替身(Test Double)與生產(chǎn)代碼表現(xiàn)可能不一致的問題。在契約測(cè)試中,契約由代碼生成,保持與現(xiàn)實(shí)同步,而且應(yīng)用可以獨(dú)立于其它應(yīng)用而僅基于契約進(jìn)行快速測(cè)試。

領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)因?yàn)槲⒎?wù)的流行而再次火了起來,契約測(cè)試也是一樣。

為了在微服務(wù)開發(fā)模式下跨團(tuán)隊(duì)協(xié)調(diào)更有效率,提升持續(xù)集成流水線自動(dòng)化水平,契約測(cè)試有效彌補(bǔ)了集成測(cè)試的不足,強(qiáng)勢(shì)C位出鏡。

本文將通過逐步介紹契約測(cè)試是什么,怎么做,有哪些工具,有哪些最佳實(shí)踐和經(jīng)驗(yàn)教訓(xùn),帶您一起徹底掌握契約測(cè)試。

什么是契約測(cè)試

契約測(cè)試(Contract testing)是一種測(cè)試技術(shù),它通過以隔離檢查集成點(diǎn)上的每個(gè)應(yīng)用的方式,確保應(yīng)用發(fā)送或接收的消息符合調(diào)用雙方共識(shí),并允許隨著時(shí)間的推移進(jìn)行演化。

為什么要做契約測(cè)試

契約測(cè)試主要解決在存在溝通邊界情況下,測(cè)試替身(Test Double)與生產(chǎn)代碼表現(xiàn)可能不一致的問題。在契約測(cè)試中,契約由代碼生成,保持與現(xiàn)實(shí)同步,而且應(yīng)用可以獨(dú)立于其它應(yīng)用而僅基于契約進(jìn)行快速測(cè)試。

由于集成測(cè)試容易受到網(wǎng)絡(luò)緩慢或不可靠,以及服務(wù)不可靠等因素的影響而運(yùn)行緩慢或失敗,所以通常會(huì)引入測(cè)試替身來代替真實(shí)外部服務(wù),以快速完成覆蓋度更廣的測(cè)試,讓測(cè)試真正起到作用。

但是,這樣做的同時(shí),帶來了測(cè)試替身是否可以持續(xù)準(zhǔn)確表示外部服務(wù)的問題。于是,需要單獨(dú)補(bǔ)充運(yùn)行一組契約測(cè)試,來檢查所有對(duì)測(cè)試替身調(diào)用的返回結(jié)果總是與對(duì)外部服務(wù)調(diào)用的返回結(jié)果相同。

契約測(cè)試的定位

金字塔模型是構(gòu)建健康、快速、可維護(hù)測(cè)試集的成熟理論。

契約測(cè)試適合歸屬于服務(wù)測(cè)試(Service Tests)層,因?yàn)樗鼈儓?zhí)行得很快,也不需要和外部服務(wù)集成來運(yùn)行。契約測(cè)試運(yùn)行于發(fā)布版本之前,為成功集成提供信心。

契約測(cè)試的價(jià)值

眾所周知,越是在項(xiàng)目生命周期的后期發(fā)現(xiàn)Bug,其修復(fù)的成本就越高。

不同于端到端(E2E)測(cè)試,契約測(cè)試可以在開發(fā)人員推送代碼之前運(yùn)行,在開發(fā)階段提早發(fā)現(xiàn)問題。

契約測(cè)試還有很多端到端測(cè)試不具備的好處:

  • 不需要調(diào)用其它組件,運(yùn)行得很快。
  • 編寫測(cè)試不需要了解系統(tǒng)全貌,更容易維護(hù)。
  • 問題只存在于被測(cè)試組件中,更容易調(diào)試和修復(fù)。
  • 極易反復(fù)運(yùn)行。
  • 每個(gè)組件獨(dú)立測(cè)試,不會(huì)引發(fā)流水線構(gòu)建時(shí)間大幅增長(zhǎng)。

引入契約測(cè)試,還會(huì)帶來如下福利:

  • 在提供者API就緒之前就可以開發(fā)消費(fèi)者應(yīng)用。
  • 為提供者供應(yīng)準(zhǔn)確的需求
  • 會(huì)收獲一組文檔化良好的用例,它們確切地顯示了如何使用提供者。
  • 提供者對(duì)API變更更有信息,可以準(zhǔn)確知道使用者感興趣的字段,方便地移除未使用的字段,以及添加新的字段。
  • 對(duì)提供者API進(jìn)行修改,可以立即看到會(huì)影響哪些使用者。

沒有兩個(gè)團(tuán)隊(duì)是完全一樣的,契約測(cè)試也不是萬能的,關(guān)鍵要看契約測(cè)試可以為團(tuán)隊(duì)和項(xiàng)目帶來什么。

契約測(cè)試適合的場(chǎng)景

契約測(cè)試可以用于任何需要通信的兩個(gè)服務(wù),比如Web前端與后端API服務(wù)。

在微服務(wù)架構(gòu)體系中,因?yàn)榇嬖诟鄨F(tuán)隊(duì)獨(dú)立、服務(wù)間調(diào)用及服務(wù)單獨(dú)演進(jìn)的情形,契約測(cè)試有了更好更大的用武之地。良好的契約測(cè)試,使得開發(fā)人員很容易避免版本地獄,是微服務(wù)開發(fā)和部署的利器。

概念術(shù)語(yǔ)

契約測(cè)試主要涉及如下概念術(shù)語(yǔ):

  • 消費(fèi)者(Consumer):對(duì)于調(diào)用,發(fā)起請(qǐng)求的一方。對(duì)于MQ,為接收消息的一方。
  • 提供者(Provider):對(duì)于調(diào)用,響應(yīng)請(qǐng)求的一方。對(duì)于MQ,為生成消息的一方。
  • 契約(Contract):消費(fèi)者和提供者之間的共識(shí),是一系列交互的集合。對(duì)于HTTP調(diào)用,包括描述消費(fèi)者向提供者發(fā)送什么的預(yù)期請(qǐng)求,以及描述消費(fèi)者希望提供者返回的最小期望響應(yīng)。對(duì)于消息交互,則描述消費(fèi)者希望得到的最小期望消息。

契約測(cè)試模式

契約測(cè)試分為消費(fèi)者驅(qū)動(dòng)(consumer-driven)和提供者驅(qū)動(dòng)(Provider-driven)兩種模式。

消費(fèi)者驅(qū)動(dòng)更具哲學(xué)意義,將API的消費(fèi)者置于設(shè)計(jì)過程的核心,來倡導(dǎo)更好的內(nèi)部微服務(wù)設(shè)計(jì)。該模式的優(yōu)點(diǎn)在于,只有消費(fèi)者正在使用的部分會(huì)得到測(cè)試,而提供者可以自由地更改消費(fèi)者不使用的任何其它部分,而不必破壞任何現(xiàn)有測(cè)試。

提供者驅(qū)動(dòng)思路較為常規(guī),更適合開放數(shù)據(jù)或系統(tǒng)的場(chǎng)景。

無論采用哪種風(fēng)格,關(guān)鍵在于獲得契約測(cè)試的好處,實(shí)現(xiàn)引入契約測(cè)試的目的。

契約測(cè)試基本步驟

1、消費(fèi)者驅(qū)動(dòng)

消費(fèi)者驅(qū)動(dòng)的契約測(cè)試運(yùn)行步驟如下:

  • 消費(fèi)者基于提供者的mock編寫和執(zhí)行消費(fèi)者測(cè)試
  • 消費(fèi)者方通過消費(fèi)者測(cè)試生成契約,并將契約共享給提供者
  • 提供者根據(jù)契約編寫測(cè)試

2、提供者驅(qū)動(dòng)

提供者驅(qū)動(dòng)模式由提供者定義契約并驅(qū)動(dòng)整個(gè)過程。

契約測(cè)試工具

流行的契約測(cè)試工具為:

  • Pact:是一個(gè)命令行工具,反饋時(shí)間更短,有助于消費(fèi)者和生產(chǎn)者之間更好地溝通。
  • Spring Cloud Contract:主要用于JVM環(huán)境,也容易擴(kuò)展到非JVM環(huán)境,主要適用于生產(chǎn)者驅(qū)動(dòng)的契約測(cè)試。

利用Pact進(jìn)行消費(fèi)者驅(qū)動(dòng)的測(cè)試

利用Pact進(jìn)行契約測(cè)試的整個(gè)流程示意如下。

1、消費(fèi)者生產(chǎn)代碼

//消費(fèi)者期望從提供者處獲得的User數(shù)據(jù)類
data class User(
val name: String,
val lastName: String,
val age: String,
)

//消費(fèi)者處調(diào)用提供者獲取User對(duì)象的客戶端類
@Service
class UserClient {
fun getUser(): User {
return RestTemplate().exchange(
providerBaseUrl + "/user",
HttpMethod.GET,
HttpEntity(Headers()),
User::class.java

2、為消費(fèi)者編寫測(cè)試

@PactFolder("target/pacts") //存儲(chǔ)pact文件的位置
@ExtendWith(PactConsumerTestExt::class, SpringExtension::class)
class ConsumerContractTest {

//@Pact接受提供者名稱、消費(fèi)者名稱兩個(gè)參數(shù)
@Pact(provider = "user-provider-service", consumer = "user-consume-service")
fun userPact(builder: PactDslWithProvider): RequestResponsePact {
//使用pact DSL創(chuàng)建一個(gè)期望的響應(yīng)體樣本
val responseBody = LambdaDsl.newJsonBody { user ->
user.stringType("name", "someName")
user.stringType("age", "20")
user.stringType("lastName", "someLastName")
}

//使用pact DSL構(gòu)建請(qǐng)求流。當(dāng)提供者接收到GET /user請(qǐng)求時(shí),使用上面定義的樣本進(jìn)行響應(yīng)
return builder
.given("a user is present") //定義提供者狀態(tài)
.uponReceiving("a request to get user")
.pathFromProviderState("/user", "/user")
.method("GET")
.willRespondWith()
.body(responseBody.build())
.toPact()
}

//測(cè)試
//使用者向提供者M(jìn)ockServer發(fā)起請(qǐng)求,并對(duì)響應(yīng)體進(jìn)行斷言
@Test
fun `should return user`(mockServer: MockServer) {
val url = mockServer.getUrl() + "/user"

val user = UserClient().getUser(url)

//斷言key,而不是value,提升健壯性
assertTrue(user.hasProperty("age"))
assertTrue(user.hasProperty("name"))
assertTrue(user.hasProperty("lastName"))
}
}

3、生成契約文件

一旦上面的測(cè)試通過了,就會(huì)在 target/pacts 文件夾中生成一個(gè)pact契約文件,文件名稱為user-consume-service-user-provider-service.json,文件內(nèi)容如下:

{
"provider": {
"name": "user-provider-service"
},
"consumer": {
"name": "user-consume-service"
},
"interactions": [
{
"description": "a request to get user",
"request": {
"method": "GET",
"path": "/user",
"generators": {
"path": {
"type": "ProviderState",
"expression": "/user",
"dataType": "STRING"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json; charset=UTF-8"
},
"body": {
"lastName": "someLastName",
"name": "someName",
"age": "20"
},
"matchingRules": {
"body": {
"$.name": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.age": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
},
"$.lastName": {
"matchers": [
{
"match": "type"
}
],
"combine": "AND"
}
},
"header": {
"Content-Type": {
"matchers": [
{
"match": "regex",
"regex": "application/json(;\\s?charset=[\\w\\-]+)?"
}
],
"combine": "AND"
}
}
}
},
"providerStates": [
{
"name": "a user is present"
}
]
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "4.1.9"
}
}
}

4、利用Broker共享契約

Pact Broker是一個(gè)用于共享消費(fèi)者驅(qū)動(dòng)的契約,并驗(yàn)證結(jié)果的應(yīng)用程序,對(duì)于Pact創(chuàng)建的契約做了優(yōu)化,但也可以用于任何可以序列化為JSON的契約。

Pact Broker既支持在云上使用,也可以在本地部署。

以下是一個(gè)利用Docker Compose來本地部署Pack Broker的描述文件。

version: '3'

services:
postgres:
image: postgres
healthcheck:
test: psql postgres --command "select 1" -U postgres
ports:
- "5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: postgres

broker_app:
image: dius/pact-broker
ports:
- "80:80"
links:
- postgres
environment:
PACT_BROKER_DATABASE_USERNAME: postgres
PACT_BROKER_DATABASE_PASSWORD: password
PACT_BROKER_DATABASE_HOST: postgres
PACT_BROKER_DATABASE_NAME: postgres
PACT_BROKER_LOG_LEVEL: DEBUG

以下是Pact Broker啟動(dòng)后的界面樣子。

接下來就可以使用pack-jvm的pactPublish命令將契約文件發(fā)布到Broker。

首先在build.gradle文件中添加需要的配置。

pact {
publish {
pactBrokerUrl = "http://localhost:80"
pactDirectory = "target/pacts"
}
}

然后,運(yùn)行如下命令發(fā)布契約。

./gradlew pactPublish

命令執(zhí)行成功后,即可在Pact Broker上看到已發(fā)布的契約。

同時(shí),可以在Broker上查看契約細(xì)節(jié)。

至此,消費(fèi)者方已完成契約創(chuàng)建、發(fā)布等全部工作。

5、提供者端驗(yàn)證

如下為提供者的生產(chǎn)代碼。

@RestController
class UserService {
@GetMapping("/user")
fun getUser(): Map<String, String> {
return mapOf(
"name" to "Foo",
"lastName" to "Bar",
"age" to "22"
)
}
}

以下代碼用于提供者對(duì)契約的驗(yàn)證。

@RunWith(SpringRestPactRunner::class)
@Provider("user-provider-service") //提供者名稱
@PactBroker(
host = "localhost",
port = "80",
scheme = "http",
consumers = ["user-consume-service"],
) //Pact Broker及消費(fèi)者信息
@SpringBootTest(classes = [PactproviderApplication::class], webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = ["server.port=8080"])
class UserServiceProviderTests {
@TestTarget
val target: HttpTarget = HttpTarget("localhost", 8080)

@MockkBean
lateinit var userService: UserService

@Before
fun setup() {
//如下配置用于讓 pact 在測(cè)試完成后,將測(cè)試結(jié)果發(fā)送到 broker
System.setProperty("pact.verifier.publishResults", "true")
}

@Test
@State("a user is present") //對(duì)應(yīng)于契約中的提供者狀態(tài),以及消費(fèi)者測(cè)試中的 given
fun `should have a customer`() {
//以下用于定義 userService 的 mock 行為
every { userService.getUser() } returns mapOf(
"name" to "someName",
"lastName" to "someLastName",
"age" to "22"
)
}
}

以下為測(cè)試運(yùn)行結(jié)果的樣子。

至此,已完成提供者測(cè)試,并證實(shí)了契約被正確履行。

在Broker上,可以看到契約被驗(yàn)證通過。

至此,消費(fèi)者、提供者一起完成了整個(gè)契約測(cè)試。

利用SCC進(jìn)行提供者驅(qū)動(dòng)的測(cè)試

利用Spring Cloud Contract進(jìn)行契約測(cè)試的過程示意如下。

SCC各組件相互關(guān)系描述如下。

1、提供者添加SCC依賴和maven插件

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

<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<extensions>true</extensions>
</plugin>

2、提供者定義契約

SCC允許通過groovy、yaml、代碼等多種方式定義契約。

契約中包含在body中定義的消息示例及在matcers中定義的字段匹配器等。

request:
method: PUT
url: /customers
body:
name: John Don
Phone: 1234567890
headers:
Content-Type: application/json
matchers:
body:
- path: $.['name']
type: by_regex
regexType: as_string
- path: $.['phone']
type: by_regex
value: "[0-9]{10}"
response:
status: 200
body:
reference: 1122334455
headers:
Content-Type: application/json
matchers:
body:
- path: $.['reference']
type: by_regex
value: "[0-9]{5}"

3、提供者驗(yàn)證契約

首先創(chuàng)建一個(gè)測(cè)試基類,以便使用命令來生成測(cè)試代碼。

測(cè)試基類的作用是在每次測(cè)試運(yùn)行之前初始化提供者API和其他配置。

public class BaseTestClass {
@BeforeEach
public void setup(){
RestAssuredMockMvc.standaloneSetup(new CustomerRestController());
}
}

接下來,框架將在maven插件被構(gòu)建過程調(diào)用后,基于契約生成測(cè)試代碼。

測(cè)試代碼將向提供者發(fā)送帶有契約中示例數(shù)據(jù)的請(qǐng)求,并解析響應(yīng),根據(jù)契約驗(yàn)證響應(yīng)。

@Test
void validate_shouldCreateCustomer() throws Exception {
// given:
MockMvcRequestSpecification request = given()
.header("Content-Type", "application/json")
.body("{\"name\":\"John Don\",\"phone\":1234567890}");

// when:
ResponseOptions response = given().spec(request)
.put("/customers");

// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['reference']").matches("[0-9]{5}");
}

4、存儲(chǔ)契約

提供者生成的契約jar文件,可以發(fā)布到Nexus、JFrog等構(gòu)建庫(kù)中存儲(chǔ),同時(shí)和其他maven構(gòu)建一樣,可以通過group Id、artifact id、version等識(shí)別和獲得。

5、消費(fèi)者驗(yàn)證契約

在消費(fèi)者端,SCC框架為其提供了一個(gè)Stub Runner,它可以獲取存根定義并將其注冊(cè)到WireMock服務(wù)器。而該Mock服務(wù)器將模擬測(cè)試用例提供者的API,以支持消費(fèi)者驗(yàn)證契約。

因此,首先需要為消費(fèi)者應(yīng)用添加stub runner的maven依賴。

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

以下為消費(fèi)者契約測(cè)試代碼。

其中@AutoConfigureStubRunner用于指定契約存根的maven組件信息,包括group id、artifact id和端口號(hào)。然后stub runner就可以從本地或遠(yuǎn)程存儲(chǔ)庫(kù)獲取存根定義。

如下測(cè)試代碼將調(diào)用端口為6565的mock服務(wù)器,并對(duì)響應(yīng)進(jìn)行斷言。

@SpringJUnitConfig(CustomerClient.class)
@AutoConfigureStubRunner(ids = {"space.gavinklfong.demo:customer-service:+:stubs:6565"},
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class CustomerClientIntegrationTests {

private CustomerClient customerClient;
@BeforeEach
void setup(){
customerClient = new CustomerClient(“http://localhost:6565);
}

@Test
void testCustomerCreation(){
Customer customer = new Customer(“Peter Pan”, 2233445566);
String ref = customerClient.create(customer);
assertNotNull(ref);
assertEquals(5, ref.length());
assertTrue(isNumeric(ref));
}

}

最佳實(shí)踐

契約測(cè)試有如下最佳實(shí)踐。

  • 契約測(cè)試的關(guān)注點(diǎn)應(yīng)該是請(qǐng)求和響應(yīng)的消息,而不是其行為
  • 契約測(cè)試應(yīng)該與數(shù)據(jù)無關(guān)
  • 基于Broker將整個(gè)過程與CI集成
  • pact用于契約測(cè)試,而不是功能測(cè)試
  • 只針對(duì)那些一旦發(fā)生變化就會(huì)影響消費(fèi)者的事情進(jìn)行斷言
  • 將最新的契約提供給提供者

經(jīng)驗(yàn)教訓(xùn)

1、讓所有人都上船

契約測(cè)試需要跨團(tuán)隊(duì)協(xié)作,盡快讓各方都加入進(jìn)來,就模式和工具等各方面達(dá)成一致,否則很快就會(huì)遇到麻煩。

2、不要低估學(xué)習(xí)曲線

契約測(cè)試是一種新型的測(cè)試方法,即使擁有豐富的單元測(cè)試、集成測(cè)試等其它測(cè)試類型的豐富經(jīng)驗(yàn),也并不能代表可以編寫有價(jià)值、可維護(hù)的契約測(cè)試,真正的挑戰(zhàn)在于如何處理API和契約隨時(shí)間的變化。

3、溝通仍然是必要的

工具并不能代替彼此之間的交流,契約測(cè)試也是一樣,至少在初始階段。而在后期,消費(fèi)者更新契約之前,仍然有必要事先與提供者進(jìn)行討論。

4、Pact更好用

Spring Cloud Contract不太適合消費(fèi)者驅(qū)動(dòng)的契約測(cè)試,總是需要消費(fèi)者等待提供者完成相關(guān)工作,而Pact則不需要這種等待。

另外,SCC使用Groovy編寫的契約需要手動(dòng)與消費(fèi)者代碼保持同步,而Pact的API則相當(dāng)成熟,會(huì)自動(dòng)化生成各種代碼和文件。同時(shí)SCC在提供者端提供的的設(shè)置和斷言選項(xiàng)較少。Pact的社區(qū)支持也相對(duì)較好。

Pact對(duì)多語(yǔ)言的支持也更好。

小技巧

可以使用swagger-diff工具比較兩個(gè)版本的API

swagger-diff工具可以用來比較兩個(gè)Swagger API規(guī)范,并將結(jié)果輸出到HTML或Markdown文件中。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2023-08-01 09:27:44

Golang模糊測(cè)試

2023-12-21 17:11:21

Containerd管理工具命令行

2022-12-20 07:39:46

2021-05-12 18:22:36

Linux 內(nèi)存管理

2023-12-15 09:45:21

阻塞接口

2020-10-09 07:56:52

Linux

2023-10-24 11:44:21

2018-06-07 13:17:12

契約測(cè)試單元測(cè)試API測(cè)試

2020-12-18 11:54:22

Linux系統(tǒng)架構(gòu)

2021-02-22 09:05:59

Linux字符設(shè)備架構(gòu)

2021-06-04 09:35:05

Linux字符設(shè)備架構(gòu)

2023-03-10 07:57:26

2024-11-19 09:00:00

Pythondatetime模塊

2025-04-18 05:50:59

Spring接口Aware

2017-11-28 15:20:27

Python語(yǔ)言編程

2023-08-24 16:50:45

2022-07-27 11:51:39

契約測(cè)試開發(fā)測(cè)試

2022-10-21 18:31:21

ETL

2022-08-04 14:38:49

vue3.2setup代碼

2019-03-18 11:15:07

Linux性能網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)