首页 > 技术文章 > 契约测试实战(python)

jiaoyaxiong 2021-03-24 20:49 原文

简介

契约测试的背景就是微服务大行其道

契约测试最开始的概念由 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)

  

 

  1. python setup.py build
  2. 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 

推荐阅读