±à¼ÍƼö: |
±¾ÎÄÀ´×ÔÓÚ²©¿ÍרÀ¸£¬±¾ÎÄÖ÷Òª½éÉÜÁËÏû·ÑÕßÇý¶¯µÄÆõÔ¼²âÊÔ£¬´ÓÏû·ÑÕßÒµÎñʵÏֵĽǶȳö·¢£¬Çý¶¯³öÆõÔ¼£¬ÔÙ»ùÓÚÆõÔ¼£¬¶ÔÌṩÕßÑéÖ¤µÄÒ»ÖÖ²âÊÔ·½Ê½¡£ |
|
ΪʲôҪ×öÆõÔ¼²âÊÔ
¼ÙÉèÎÒÃÇÓÐÒ»¸öÓɶà¸ö΢·þÎñ×é³ÉµÄϵͳ£ºÈçͼ

Èç¹ûÎÒÃÇÏë²âÊÔÓ¦ÓÃv1£¬ÎÒÃÇ¿ÉÒÔ×öÒÔÏÂÁ½¼þÊÂÖ®Ò»£º
²¿ÊðËùÓÐ΢·þÎñ²¢Ö´Ðж˵½¶Ë²âÊÔ¡£
ÔÚµ¥Ôª/¼¯³É²âÊÔÖÐÄ£ÄâÆäËû΢·þÎñ¡£
Á½Õß¶¼ÓÐÆäÓŵ㣬µ«Ò²Óкܶàȱµã¡£
²¿ÊðËùÓÐ΢·þÎñ²¢Ö´Ðж˵½¶Ë²âÊÔ
Óŵ㣺
Ä£ÄâÉú²ú¡£
²âÊÔ·þÎñÖ®¼äµÄÕæÊµÍ¨ÐÅ¡£
ȱµã£º
Òª²âÊÔÒ»¸ö΢·þÎñ£¬ÎÒÃDZØÐ벿Êð6¸ö΢·þÎñ£¬¼¸¸öÊý¾Ý¿âµÈ¡£
ÔËÐÐʱ¼äºÜ³¤£¬Îȶ¨ÐԲÈÝÒ×ʧ°Ü¡£
·Ç³£ÄÑÒÔµ÷ÊÔ£¬ÒÀÀµ·þÎñ²»ÊÜ¿ØÖÆ¡£
ÔÚµ¥Ôª/¼¯³É²âÊÔÖÐÄ£ÄâÆäËû΢·þÎñ
Óŵ㣺
·Ç³£¿ìËٵķ´À¡£¬¼òµ¥Ò×Óá£
ËûÃÇûÓлù´¡ÉèʩҪÇó,ÈçDB£¬ÍøÂçµÈ¡£
ȱµã£º
Ä£Äâ²»¹»ÕæÊµ¡£
²¿·Ö³¡¾°²âÊÔ²»µ½¡£
ʹÓÃSpring Cloud Contractºó
ÈçÏ£º²âÊÔv1¾Í²»ÓÃÆô¶¯ÆäËü·þÎñÁË

ÆõÔ¼²âÊÔ£¨Contract£©
ÆõÔ¼²âÊÔ²½Öè
Spring Cloud ContractÆõÔ¼²âÊÔ´ó¸Å·ÖÈý¸ö²½Öè
producerÌṩ·þÎñµÄ¶¨ºÃ·þÎñ½Ó¿Ú£¨¼´ÆõÔ¼£©
Éú³Éstub£¬²¢¹²Ïí¸øÏû·Ñ·½£¬¿Éͨ¹ýmvn installµ½maven¿âÖÐ
consumerÏû·Ñ·½ÒýÓÃÆõÔ¼·þÎñ£¬½øÐм¯³É²âÊÔ
Server/Producer ·þÎñÌṩ¶Ë
¹¹¼ÜÒýÈë
ÔÚpom.xmlÖмÓÈëjar°üÒÀÀµ£¬·ÅÈëdependenciesÖÐ
<!--ÆõÔ¼²âÊÔ·þÎñÌṩ¶ËÒÀÀµ-->
<dependency> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency> |
ÅäÖòå¼þ£¬·ÅÈëpluginsÖÐ
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<!-- Don't forget about this value !! -->
<extensions>true</extensions>
<configuration>
<!-- MvcMockTestΪÉú³É±¾µØ²âÊÔ°¸ÀýµÄ»ùÀà -->
<baseClassForTests>com.springboot.services.producer
.MvcMockTest</baseClassForTests>
</configuration>
</plugin> |
spring-cloud-contract-maven-pluginµÄʹÓýéÉÜ
spring-cloud-contract:help£º°ïÖú
spring-cloud-contract:convert£ºÔÚtarget/stubsÏ£¬¸ù¾Ý½«ÆõÔ¼Éú³ÉmappingÎļþ£¬ÓÃÓÚ´ò°üjarÎļþ£¬Ìṩhttp·þÎñ£¬¹©consumerʹÓÃ
spring-cloud-contract:generateStubs£ºÉú³ÉstubsµÄjar°ü£¬ÓÃÓÚ·ÖÏí¸øconsumerʹÓÃ
spring-cloud-contract:generateTests£º»ùÓÚcontractÉú³É·þÎñÆõÔ¼µÄ²âÊÔ°¸Àý£¬·þÎñʵÏÖÁËÆõÔ¼ºó£¬±£Ö¤ÊµÏÖÓëÆõÔ¼Ò»ÖÂ
spring-cloud-contract:run£ºÆô¶¯ÆõÔ¼·þÎñ£¬½«ÆõÔ¼±©Â¶Îªhttp server·þÎñ
spring-cloud-contract:pushStubsToScm£º½«ÆõÔ¼·ÅÖÃÔÚscmÖйÜÀí
ÐèÇó
¼ÙÉèÓÐÐèÇó producer·þÎñÐèÒªÌṩһ¸ö¶ÔÍâµÄ½Ó¿ÚGETÇëÇóµÄ½Ó¿Ú£¬²¢ÇÒ½ÓÊÕÒ»¸önameµÄ²ÎÊý£¬Èç
GET /hello?name=zhangsan£¬ÒªÇó·µ»Ø{"code": "000000","mesg":
"´¦Àí³É¹¦"}
1. дController
×¢£º¶ÔÏóResult¶ÔÏó·â×°ÁË{"code": "000000","mesg":
"´¦Àí³É¹¦"} Êý¾Ý
package com.springboot.services.producer.rest;
import com.springboot.cloud.common.core.entity.
vo.Result;
import org.springframework.web.bind.annotation.*;
import static org.apache.commons.lang. RandomStringUtils. randomNumeric;
@RestController
public class HelloController {
@RequestMapping(method = RequestMethod.GET,
value = "/hello")
public Result world(@RequestParam String name)
{
return Result.success(name);
}
|
2. ±àдÆõÔ¼
ÔÚsrc/test/resources/contracts/HelloController.groovy
ÖÐÔö¼ÓÆõÔ¼Îļþ£¨¿ÉÒÔÓжàÖÖ¸ñʽÈçgroovy ¡¢yaml¡¢£¬ÕâÀï²ÉÓÃgroovy£©
ÆõÔ¼ÊéдÈçÏ£º
Contract.make
{
request {
method 'GET'
url('/hello') {
queryParameters {
parameter("name", "zhangsan")
}
}
}
response {
status 200
body("""
{
"code": "000000",
"mesg": "´¦Àí³É¹¦"
}
""")
headers {
header('Content-Type': 'application/json;charset=UTF-8')
}
}
} |
3.Éú³Éstub jarÎļþ
Ö´ÐУºmvn spring-cloud-contract:convertÃüÁ»áÔÚtarget/stubsÏÂÉú³ÉÏà¹ØÎļþ

Éú³ÉµÄmappingÎļþ£¬ÑùʽÈçÏÂ
{ "id"
: "62db0b7f-72de-4c03-8e38-6874d4b433ab",
"request" : { "urlPath"
: "/hello", "method" :
"GET", "queryParameters"
: { "name" : { "equalTo"
: "zhangsan"
}
}
}, "response" : { "status"
: 200, "body" : "{\"code\":\"000000\",\"mesg\":\"´¦Àí³É¹¦\"}",
"headers" : { "Content-Type"
: "application/json;charset=UTF-8"
}, "transformers" : [ "response-template"
]
}, "uuid" : "62db0b7f-72de-4c03-8e38-6874d4b433ab"
} |
Ö´ÐУºmvn spring-cloud-contract:generateStubsÃüÁ»áÔÚtargetÏÂÉú³ÉstubsµÄjar°ü
È磺producer-0.0.1-SNAPSHOT-stubs.jar
4.°²×°stubµ½maven¿âÖÐ
µ±È»Ò²¿É½«plugin °ó¶¨µ½Ïà¹ØµÄphaseÉÏ×Ô¶¯°²×°µ½maven¿âÖÐ
Àý×Ó£º
mvn install:install-file
-DgroupId=com.springboot.cloud -DartifactId=producer
-Dversion=0.0.1-SNAPSHOT -Dpackaging=jar -Dclassifier=stubs
-Dfile=producer-0.0.1-SNAPSHOT-stubs.jar |
ÒÔÉÏ£¬½«stub jar°ü·ÖÏí¸øconsumer£¬¶Ô·½¾ÍÒªÒÔÔÚ¼¯³É²âÊÔ°¸ÀýÖÐʹÓÃÁË
5.½Ó¿ÚʵÏÖ²¢¼ìÑéÊÇ·ñ·ûºÏÆõÔ¼
Ìṩһ¸ö²âÊÔ»ùÀࣨÖ÷ÒªÓÃÓÚ°´ÕÕÆõÔ¼¶Ô½Ó¿ÚÉú³É²âÊÔ°¸Àý£¬¼ìÑé½Ó¿ÚÊÇ·ñ°´ÆõԼʵÏÖÁË£©
package com.springboot.services.producer;
import com.springboot.services.producer.rest.HelloController;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.Test;
public class MvcMockTest {
@Before
public void setup() {
RestAssuredMockMvc.standaloneSetup(new HelloController());
}
} |
Ö´ÐУºmvn spring-cloud-contract:generateTestsÃüÁ»áÔÚtarget/generated-test-sources/contractsĿ¼Ï¸ù¾ÝÆõÔ¼Éú³É²âÊÔ°¸Àý£¬ÓÃÓÚ·þÎñÌṩ·½×îºó¼ìÑéÊÇ·ñ·ûºÏÆõÔ¼¡£
Àý×Ó£º
import static
com.toomuchcoding.jsonassert.JsonAssertion.assert
ThatJson;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
import static org.springframework.cloud.contract.verifier.asser
tion.Spring
CloudContractAssertions.assertThat;
public class ContractVerifierTest extends MvcMockTest
{
@Test
public void validate_helloController() throws
Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.queryParam("name","zhangsan")
.get("/hello");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo
("application/json;charset=UTF-8");
// and:
DocumentContext parsedJson = JsonPath.parse(response.
getBody().asString()); assertThatJson(parsedJson).field
("['code']").isEqualTo("000000");
assertThatJson(parsedJson).field("['mesg']").isEqualTo
("\u5904\u7406\u6210\u529F");
}
} |
Client/Consumer ·þÎñµ÷ÓöË
¹¹¼ÜÒýÈë
jar°üÒÀÀµ£¬·ÅÈëdependenciesÖÐ
<!--ÆõÔ¼²âÊÔ·þÎñÌṩ¶ËÒÀÀµ-->
<dependency> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency> |
µ÷Ó÷½´úÂë
¼ÙÈçÏû·Ñ·½ÓнӿÚ/classes?name=xxx£¬¸Ã½Ó¿Úµ÷ÓÃÁËproducer·þÎñµÄ½ÖµÀ¿Ú/hello?name=xxx£¬´Ëʱ
ClassControllerÈçÏ£¬µ÷ÓÃClassService
package com.springboot.feign.rest;
import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.feign.service.ClassService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
public class ClassController {
@Autowired
private ClassService classService;
@GetMapping("/classes")
public Result hello(@RequestParam String name)
{
return classService.users(name);
}
} |
ClassServiceÈçÏ£¬Í¨¹ýfeiginµ÷ÓÃproducer·þÎñ
package com.springboot.feign.service;
import com.springboot.cloud.common.core.entity.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@FeignClient(name = "producer")
public interface ClassService {
@RequestMapping(value = "/hello",
method = RequestMethod.GET)
Result users(@RequestParam("name")
String name);
} |
²âÊÔ°¸Àý±àд
¼¯³É²âÊÔ°¸ÀýÈçÏ£º
package com.springboot.feign.rest;
import org.hamcrest.core.Is;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.
AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.stubrunner.spring.
AutoConfigureStubRunner;
import org.springframework.cloud.contract.stubrunner.spring.
StubRunnerProperties;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request
.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.
MockMvcResultMatchers;
@RunWith(SpringRunner.class)
//springbootµÄ²âÊÔÆô¶¯À࣬ÐèÒªÒÀÀµspring-boot-test¿â
@SpringBootTest
//³õʹ»¯²âÊÔ²âÊÔÅäÖ㬲âÊÔcontrollerÐèÒª
@AutoConfigureMockMvc
//Æô¶¯ÆõÔ¼·þÎñ£¬Ä£ÄâproduerÌṩ·þÎñ
@AutoConfigureStubRunner(ids = {"com.springboot.cloud:producer:+:stubs:8080"},
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class ClassControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void testMethod() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/classes").
param("name", "zhangsan"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("code",
Is.is("000000")));
}
} |
ids¸ñʽÈçÏ£º
groupId:artifactId:version:classifier:port
|
±íʾ´ÓÄÄÀï¼ÓÔØstubµÄjar°ü£¬ÓÐ3ÖÖ£º
CLASSPATH£º´ÓclasspathÖÐÕÒjar°ü£¬Ä¬ÈÏ
LOCAL£º´Ó±¾µØmaven¿âÖÐÕÒ
REMOTE£º´ÓÔ¶³Ì·þÎñÆ÷ÖÐÏÂÔØ£¬ÐèÒªÅäºÏgit,²¢½«repositoryRootÉ趨ֵ£¬È磺
@AutoConfigureStubRunner(
stubsMode="REMOTE",
repositoryRoot="git://https://github.com/spring-cloud-samples/spring-cloud-contract-nodejs-contracts-git.git",
ids="com.example:bookstore:0.0.1.RELEASE"
) |
ÍêÕûµÄÀý×Ó£¬Çë²é¿´github
|