契约测试实战(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
浙公网安备 33010602011771号