<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://wiki.riguz.com/index.php?action=history&amp;feed=atom&amp;title=Blog%3A%E4%BD%BF%E7%94%A8Spring_Cloud_Contract%E8%BF%9B%E8%A1%8C%E5%A5%91%E7%BA%A6%E6%B5%8B%E8%AF%95</id>
	<title>Blog:使用Spring Cloud Contract进行契约测试 - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.riguz.com/index.php?action=history&amp;feed=atom&amp;title=Blog%3A%E4%BD%BF%E7%94%A8Spring_Cloud_Contract%E8%BF%9B%E8%A1%8C%E5%A5%91%E7%BA%A6%E6%B5%8B%E8%AF%95"/>
	<link rel="alternate" type="text/html" href="https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8Spring_Cloud_Contract%E8%BF%9B%E8%A1%8C%E5%A5%91%E7%BA%A6%E6%B5%8B%E8%AF%95&amp;action=history"/>
	<updated>2026-06-02T19:45:50Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.42.3</generator>
	<entry>
		<id>https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8Spring_Cloud_Contract%E8%BF%9B%E8%A1%8C%E5%A5%91%E7%BA%A6%E6%B5%8B%E8%AF%95&amp;diff=2653&amp;oldid=prev</id>
		<title>imported&gt;Riguz：​研究了一下契约测试，这个概念听着很高端，其实解决的是一个很古老的问题：系统间的接口定义。以前我们做系统同其他系统对接的时候需要定义接口，需要去设计，去确认；尤其是当下微服务比较盛行的时候，我们自己的系统之间也增加了接口，伴随着敏捷开发的流程，很多时候接口在一开始根本都不会去设计，想到哪改到哪.....于是就出现了所谓的契约测试的东西。</title>
		<link rel="alternate" type="text/html" href="https://wiki.riguz.com/index.php?title=Blog:%E4%BD%BF%E7%94%A8Spring_Cloud_Contract%E8%BF%9B%E8%A1%8C%E5%A5%91%E7%BA%A6%E6%B5%8B%E8%AF%95&amp;diff=2653&amp;oldid=prev"/>
		<updated>2017-08-10T00:00:00Z</updated>

		<summary type="html">&lt;p&gt;研究了一下契约测试，这个概念听着很高端，其实解决的是一个很古老的问题：系统间的接口定义。以前我们做系统同其他系统对接的时候需要定义接口，需要去设计，去确认；尤其是当下微服务比较盛行的时候，我们自己的系统之间也增加了接口，伴随着敏捷开发的流程，很多时候接口在一开始根本都不会去设计，想到哪改到哪.....于是就出现了所谓的契约测试的东西。&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;
研究了一下契约测试，这个概念听着很高端，其实解决的是一个很古老的问题：系统间的接口定义。以前我们做系统同其他系统对接的时候需要定义接口，需要去设计，去确认；尤其是当下微服务比较盛行的时候，我们自己的系统之间也增加了接口，伴随着敏捷开发的流程，很多时候接口在一开始根本都不会去设计，想到哪改到哪.....于是就出现了所谓的契约测试的东西。&lt;br /&gt;
先来说说契约测试解决的问题吧：&lt;br /&gt;
&lt;br /&gt;
* consumer在依赖的provider接口没有实现的时候可以用stub模拟&lt;br /&gt;
* provider可以测试自身的接口是否满足接口定义&lt;br /&gt;
* consumer和provider都以契约为准，但接口有变动时修改契约，否则测试通不过...~&lt;br /&gt;
* 可以对边界进行测试&lt;br /&gt;
&lt;br /&gt;
大概就是这样吧，我觉得前两条是最重要的Feature，举个例子，比如我们有一个Vehicle的服务，用来根据vin(车辆底盘号）来获取车辆的信息；一个Costomer的服务需要调用这个服务来获取客户的车辆信息。我们的Vehicle接口如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
@GetMapping(&amp;quot;/vehicle/{vin}&amp;quot;)&lt;br /&gt;
VehicleDetail getVehicleDetail(@PathVariable String vin){&lt;br /&gt;
   VehicleDetail item = this.vehicleService.getVehicle(vin);&lt;br /&gt;
   if(item == null)&lt;br /&gt;
       throw new VehicleNotFoundException();&lt;br /&gt;
   return item;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
我们在Vehicle服务中定义一个契约：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
Contract.make {&lt;br /&gt;
    request {&lt;br /&gt;
        method &amp;#039;GET&amp;#039;&lt;br /&gt;
        url value(&amp;#039;/vehicle/WDC1660631A7506890&amp;#039;)&lt;br /&gt;
    }&lt;br /&gt;
    response {&lt;br /&gt;
        status 200&lt;br /&gt;
        body([&lt;br /&gt;
                vin           : &amp;#039;WDC1660631A7506890&amp;#039;,&lt;br /&gt;
                brand         : &amp;#039;Audi X5&amp;#039;,&lt;br /&gt;
                owner         : &amp;#039;James 王&amp;#039;,&lt;br /&gt;
                registeredDate: 1502347667000,&lt;br /&gt;
                mileage       : 1200&lt;br /&gt;
        ])&lt;br /&gt;
        headers {&lt;br /&gt;
            header(&amp;#039;Content-Type&amp;#039;: value(&lt;br /&gt;
                    producer(regex(&amp;#039;application/json.*&amp;#039;)),&lt;br /&gt;
                    consumer(&amp;#039;application/json&amp;#039;)&lt;br /&gt;
            ))&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这样我们在执行gradle的`generateContractTests`任务的时候会自动生成一个契约测试，我们在测试Vehicle服务的时候，只需要Mock我们的Service，返回对应的模拟信息：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
@Before&lt;br /&gt;
public void setUp() throws Exception {&lt;br /&gt;
    VehicleDetail i = new VehicleDetail();&lt;br /&gt;
    i.setVin(&amp;quot;WDC1660631A7506890&amp;quot;);&lt;br /&gt;
    i.setOwner(&amp;quot;James 王&amp;quot;);&lt;br /&gt;
    i.setBrand(&amp;quot;Audi X5&amp;quot;);&lt;br /&gt;
    i.setRegisteredDate(new Date(1502347667000L));&lt;br /&gt;
    i.setMileage(1200);&lt;br /&gt;
    RestAssuredMockMvc.webAppContextSetup(context);&lt;br /&gt;
&lt;br /&gt;
    given(vehicleService.getVehicle(&amp;quot;WDC1660631A7506890&amp;quot;)).willReturn(i);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
刚刚的契约是一个很固定的数据，我们还可以加上正则表达式的检测：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
Contract.make {&lt;br /&gt;
    request {&lt;br /&gt;
        method &amp;#039;GET&amp;#039;&lt;br /&gt;
        url value(consumer(regex(&amp;#039;/vehicle/[A-Z0-9]{18}&amp;#039;)),&lt;br /&gt;
                producer(&amp;#039;/vehicle/WDC1660631A7506890&amp;#039;))&lt;br /&gt;
    }&lt;br /&gt;
    response {&lt;br /&gt;
        status 200&lt;br /&gt;
        body([&lt;br /&gt;
                vin           : $(producer(regex(/[A-Z0-9]{18}/))),&lt;br /&gt;
                brand         : $(producer(anyNonBlankString())),&lt;br /&gt;
                owner         : $(producer(anyNonBlankString())),&lt;br /&gt;
                registeredDate: $(producer(regex(/[1-9][0-9]{11,12}/))),&lt;br /&gt;
                mileage       : $(producer(regex(/[1-9][0-9]{0,10}/)))&lt;br /&gt;
        ])&lt;br /&gt;
        headers {&lt;br /&gt;
            header(&amp;#039;Content-Type&amp;#039;: value(&lt;br /&gt;
                    producer(regex(&amp;#039;application/json.*&amp;#039;)),&lt;br /&gt;
                    consumer(&amp;#039;application/json&amp;#039;)&lt;br /&gt;
            ))&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
以及异常情况下的测试：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
Contract.make {&lt;br /&gt;
    request {&lt;br /&gt;
        method &amp;#039;GET&amp;#039;&lt;br /&gt;
        url value(consumer(regex(&amp;#039;/vehicle/\\w.+&amp;#039;)),&lt;br /&gt;
                producer(&amp;#039;/vehicle/XXXXX&amp;#039;))&lt;br /&gt;
    }&lt;br /&gt;
    response {&lt;br /&gt;
        status 404&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这样每一个groovy文件都会对应着生成一个测试，达到我们测试Provider的目的。那么，对于客户端来说，怎么测试呢？很简单，我们执行gradle的`install`命令，会把生成的stub包放到本地的gradle源中，我们在客户端测试的时候可以这么写：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;java&amp;quot;&amp;gt;&lt;br /&gt;
@RunWith(SpringRunner.class)&lt;br /&gt;
@SpringBootTest&lt;br /&gt;
@AutoConfigureStubRunner(ids = &amp;quot;com.riguz:foo:+:stubs:10000&amp;quot;, workOffline = true)&lt;br /&gt;
public class CustomerServiceTest {&lt;br /&gt;
&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private CustomerService customerService;&lt;br /&gt;
&lt;br /&gt;
    @Test&lt;br /&gt;
    public void shouldReturnCustomerDetail(){&lt;br /&gt;
        CustomerInfo info = this.customerService.getCustomerInfo(&amp;quot;123&amp;quot;);&lt;br /&gt;
        System.out.println(info);&lt;br /&gt;
        assertEquals(1, info.getVehicles().size());&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
对应着`~/.m2/repository/com/riguz/foo/1.0-SNAPSHOT/foo-1.0-SNAPSHOT-stubs.jar`，`+`表示取最新版本，`10000`是端口号，也就是模拟出了一个远程的服务端。这样如果契约有修改的话，取到新的契约stubs包也会跟着修改了。&lt;br /&gt;
&lt;br /&gt;
另外，如果单纯的想模拟一个服务端怎么办？有办法，我们在provider中执行gradle的`generateClientStubs`命令后，会生成一个mappings目录，在`build/stubs/....`下面。里面有一些json文件，例如我们的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;id&amp;quot; : &amp;quot;5dd47b81-a184-4b9e-be02-b6e22c409c81&amp;quot;,&lt;br /&gt;
  &amp;quot;request&amp;quot; : {&lt;br /&gt;
    &amp;quot;url&amp;quot; : &amp;quot;/vehicle/WDC1660631A7506890&amp;quot;,&lt;br /&gt;
    &amp;quot;method&amp;quot; : &amp;quot;GET&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;response&amp;quot; : {&lt;br /&gt;
    &amp;quot;status&amp;quot; : 200,&lt;br /&gt;
    &amp;quot;body&amp;quot; : &amp;quot;{\&amp;quot;vin\&amp;quot;:\&amp;quot;WDC1660631A7506890\&amp;quot;,\&amp;quot;brand\&amp;quot;:\&amp;quot;Audi X5\&amp;quot;,\&amp;quot;owner\&amp;quot;:\&amp;quot;James \\u738b\&amp;quot;,\&amp;quot;registeredDate\&amp;quot;:1502347667000,\&amp;quot;mileage\&amp;quot;:1200}&amp;quot;,&lt;br /&gt;
    &amp;quot;headers&amp;quot; : {&lt;br /&gt;
      &amp;quot;Content-Type&amp;quot; : &amp;quot;application/json&amp;quot;&lt;br /&gt;
    },&lt;br /&gt;
    &amp;quot;transformers&amp;quot; : [ &amp;quot;response-template&amp;quot; ]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;uuid&amp;quot; : &amp;quot;5dd47b81-a184-4b9e-be02-b6e22c409c81&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
我们可以通过wiremock-standalone来启动一个模拟的服务端。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
java -jar wiremock-standalone-2.7.1.jar&lt;br /&gt;
# 启动后会自动创建一个mappings目录，把我们生成的mappings目录中的内容拷贝进去，再重新运行即可&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
这样访问`http://localhost:8080/vehicle/WDC1660631A7506890`就可以得到我们的契约里面写的模拟数据了。&lt;br /&gt;
&lt;br /&gt;
好了，如果有疑问请参考[https://github.com/soleverlee/spring-contract-example.git 完整的代码]，建议参考末尾的参考文章，本文不过是跟着写了一下而已。&lt;br /&gt;
&lt;br /&gt;
总结一下吧，其实并没有感觉到Contract Test有多高端，不过很适用与微服务+敏捷开发这种场合。来说说我觉得不足的地方：&lt;br /&gt;
&lt;br /&gt;
* 契约测试依然是测试，无法替代设计，如果设计的接口是一坨*测试的再好又有什么呢；并不是反对测试，而是感觉但凡重视测试的同时容易轻视设计（或者说测试能力要比设计能力强太多...)&lt;br /&gt;
* 如果是同三方系统对接，如何来操作呢？&lt;br /&gt;
* 对于一些其他的客户端就勉为其难了，比如NodeJS的客户端，无法使用生成的stubs.jar文件，客户端怎么保证得到的东西是自己想要的结果?&lt;br /&gt;
&lt;br /&gt;
参考文章:&lt;br /&gt;
&lt;br /&gt;
* [Consumer-Driven Contract Testing with Spring Cloud Contract&lt;br /&gt;
](https://specto.io/blog/2016/11/16/spring-cloud-contract/)&lt;br /&gt;
* [http://cloud.spring.io/spring-cloud-contract/spring-cloud-contract.html Spring Cloud Contract Document]&lt;/div&gt;</summary>
		<author><name>imported&gt;Riguz</name></author>
	</entry>
</feed>