契约测试实战(python)
简介
契约测试的背景就是微服务大行其道
契约测试最开始的概念由 Martin Fowler 提出,它又被称之为:消费者驱动的契约测试(Consumer Driven Contracts),简称CDC。这里的契约是指软件系统中各个服务间交互的数据标准格式,更多的指消费端(client)和提供端(server)之间交互的API的格式。
契约测试一般分两种,一种是消费者驱动,一种是提供者驱动。其中最常用的,是消费者驱动的契约测试(简称 CDC)。即由“消费者”定义出接口“契约”,然后测试“提供者”的接口是否符合契约。
官网:
github:https://github.com/pact-foundation/pact-python
安装:
pip install --trusted-host https://repo.huaweicloud.com -i https://repo.huaweicloud.com/repository/pypi/simple pact-python
mac m1 目前无法安装成功-卒。(中间需要下载github的文件,下载太慢)
connect 发起TCP连接请求被拒绝是由于目标服务器上无对应的监听套接字(IP && PORT)。
你以为这就结束了?
我又换了个包:pactman
pact-python 有个坑爹的就是其部分实现用了ruby!
pactman vs pact-python The key difference is all functionality is implemented in Python, rather than shelling out or forking to the ruby implementation. This allows for a much nicer mocking user experience (it mocks urllib3 directly), is faster, less messy configuration (multiple providers means multiple ruby processes spawned on different ports). Where pact-python required management of a background Ruby server, and manually starting and stopping it, pactman allows a much nicer usage like: import requests from pactman import Consumer, Provider pact = Consumer('Consumer').has_pact_with(Provider('Provider')) def test_interaction(): pact.given("some data exists").upon_receiving("a request") \ .with_request("get", "/", query={"foo": ["bar"]}).will_respond_with(200) with pact: requests.get(pact.uri, params={"foo": ["bar"]}) It also supports a broader set of the pact specification (versions 1.1 through to 3). The pact verifier has been engineered from the start to talk to a pact broker (both to discover pacts and to return verification results). There’s a few other quality of life improvements, but those are the big ones.
安装:
pip install pactman
https://pypi.org/project/pactman/
感觉pactman 也要被放弃了。。。放弃尝试-卒
下载的ruby包,需要修改pact-python 的安装代码
github太慢
path is /Users/jiaoyaxiong/.pyenv/versions/3.9.2/lib/python3.9/site-packages/pact/bin/osx.tar.gz
需要去公司下载:
https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v1.88.3/pact-1.88.3-osx.tar.gz
摸鱼:
在公司折腾了下,看了下源码,启动服务等待之前需要加点延时,我这启动的慢。。。。
jiaoyaxiong ~/PycharmProjects/sgw-auto-test/tdc/pact_test$ pact-verifier --provider-base-url=http://127.0.0.1:8080 --pact-url=./pacts/moduleb-modulea.json
INFO: Reading pact at ./pacts/moduleb-modulea.json
Verifying a pact between ModuleB and ModuleA
Given test service.
a request for serviceB
with GET /
returns a response which
WARN: Skipping set up for provider state 'test service.' for consumer 'ModuleB' as there is no --provider-states-setup-url specified.
has status code 200
has a matching body
1 interaction, 0 failures
jiaoyaxiong ~/PycharmProjects/sgw-auto-test/tdc/pact_test$
本地M1 搞起来
源码安装,需要修改源码:
源码下载地址:https://pypi.org/project/pact-python/#files
注释掉下载github代码,添加拷贝本机已下载好的ruby包
from urllib import urlopen else: from urllib.request import urlopen path = os.path.join(bin_path, suffix) # resp = urlopen(uri.format(version=PACT_STANDALONE_VERSION, suffix=suffix)) # with open(path, 'wb') as f: # if resp.code == 200: # f.write(resp.read()) # else: # raise RuntimeError( # 'Received HTTP {} when downloading {}'.format( # resp.code, resp.url)) cmd1="cp /Users/jiaoyaxiong/Downloads/pact-1.88.3-osx.tar.gz %s " % path print(cmd1) os.system(cmd1) if 'windows' in platform.platform().lower(): with ZipFile(path) as f: f.extractall(bin_path) else: with tarfile.open(path) as f: f.extractall(bin_path)
- python setup.py build
- python setup.py install
然后写契约文件
# -*- coding: utf-8 -*- """ @author: jiaoyaxiong @site: https://www.cnblogs.com/jiaoyaxiong @email: yaxiongjiao@qq.com @time: 2021/3/25 8:49 下午 """ import atexit import requests import unittest from pact.consumer import Consumer from pact.provider import Provider # 定义一个pact,ca,pa,契约文件存放在pacts文件夹下 pact = Consumer('ca').has_pact_with(Provider('pa'), pact_dir='./pacts') # 启动服务 pact.start_service() atexit.register(pact.stop_service) # 测试用例 class UserTesting(unittest.TestCase): def test_service(self): # 消费者定义的期望结果 expected = {"name": "zhangsan", "age": 30} # 消费者定义的契约的实际内容。包括请求参数、请求方法、请求头、响应值等 (pact .given('test service.') .upon_receiving('a request for serviceB') .with_request('get', '/') .will_respond_with(200, body=expected)) # pact自带一个mock服务,端口 1234 # 用requests向mock接口发送请求,验证mock的结果是否正确 with pact: res = requests.get("http://localhost:1234").json() self.assertEqual(res, expected) if __name__ == "__main__": ut = UserTesting() ut.test_service()
运行后生成契约:
{ "consumer": { "name": "ca" }, "provider": { "name": "pa" }, "interactions": [ { "description": "a request for serviceB", "providerState": "test service.", "request": { "method": "get", "path": "/" }, "response": { "status": 200, "headers": { }, "body": { "name": "zhangsan", "age": 30 } } } ], "metadata": { "pactSpecification": { "version": "2.0.0" } } }
然后写个生产者
# -*- coding: utf-8 -*- """ @author: jiaoyaxiong @site: https://www.cnblogs.com/jiaoyaxiong @email: yaxiongjiao@qq.com @time: 2021/3/25 9:13 下午 """ import json from flask import Flask app = Flask(__name__) @app.route('/') def get_info(): info = { "name": "zhangsan", "age": 30 } return info if __name__ == '__main__': app.run(port=8080)
然后把生产者运行起来,由pact 的验证期去模拟请求:
jiaoyaxiong@192 pact_test % pact-verifier --provider-base-url=http://127.0.0.1:8080 --pact-url=./pacts/ca-pa.json /Users/jiaoyaxiong/.pyenv/versions/3.9.1/lib/python3.9/site-packages/pact/bin/pact/lib/ruby/lib/ruby/gems/2.2.0/gems/bundler-1.9.9/lib/bundler/shared_helpers.rb:78: warning: Insecure world writable dir /opt/homebrew/Cellar in PATH, mode 040777 INFO: Reading pact at ./pacts/ca-pa.json Verifying a pact between ca and pa Given test service. a request for serviceB with GET / returns a response which WARN: Skipping set up for provider state 'test service.' for consumer 'ca' as there is no --provider-states-setup-url specified. has status code 200 has a matching body 1 interaction, 0 failures jiaoyaxiong@192 pact_test %
契约测试最重要的还是测试两者之间的契约,契约变动可能会影响消费者。
代码:https://gitee.com/jiaoyaxiong/review-python/tree/master/pact_test