- PVSM.RU - https://www.pvsm.ru -
Цели данной публикации:
Consumer Driven Contracts
В этой части мы пройдемся по основным моментам CDC. Данная статья не является исчерпывающей на тему контрактного тестирования. Существует достаточное количество материалов на эту тему на том же Хабре [1].
Для продолжения нам необходимо познакомиться с основными положениями CDC:
Итак, начнем с теста на стороне потребителя [4]. Я использовал Pactman [5]. Вот так выглядит тест:
import pytest
from pactman import Like
from model.client import Client
@pytest.fixture()
def consumer(pact):
return Client(pact.uri)
def test_app(pact, consumer):
expected = '123456789'
(pact
.given('provider in some state')
.upon_receiving("request to get user's phone number")
.with_request(
method='GET',
path=f'/phone/john',
)
.will_respond_with(200, body=Like(expected))
.given('provider in some state')
.upon_receiving("request to get non-existent user's phone number")
.with_request(
method='GET',
path=f'/phone/micky'
)
.will_respond_with(404)
)
with pact:
consumer.get_users_phone(user='john', host=pact.uri)
consumer.get_users_phone(user='micky', host=pact.uri)
Используя Pact DSL, мы описываем взаимодействия request/response. После запуска теста мы получаем новый файл ({consumer}-{provider}-pact.json):
{
"consumer": {
"name": 'basic_client'
},
"provider": {
"name": 'basic_flask_app'
},
"interactions": [
{
"providerStates": [
{
"name": "provider in some state",
"params": {}
}
],
"description": "request to get user's phone number",
"request": {
"method": "GET",
"path": "/phone/john"
},
"response": {
"status": 200,
"body": "123456789",
"matchingRules": {
"body": {
"$": {
"matchers": [
{
"match": "type"
}
]
}
}
}
}
},
{
"providerStates": [
{
"name": "provider in some state",
"params": {}
}
],
"description": "request to get non-existent user's phone number",
"request": {
"method": "GET",
"path": "/phone/micky"
},
"response": {
"status": 404
}
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
}
}
}
Далее, нам нужно передать пакт провайдеру для верификации. Это делается с помощью Pact Broker.
Pact Broker — это хранилище контрактов с некоторыми дополнительными функциями, которые позволяют нам отслеживать совместимость версий сервисов, а также генерировать network diagrams (взаимодействие сервисов).
Pact Broker
Пакт
Матрица версий
Проверка провайдера
Эта часть теста полностью выполнена силами фреймворка. После проверки результаты отправляются обратно в Pact Broker.
provider-verifier_1 | Verifying a pact between basic_client and basic_flask_app
provider-verifier_1 | Given provider in some state
provider-verifier_1 | request to get user's phone number
provider-verifier_1 | with GET /phone/john
provider-verifier_1 | returns a response which
provider-verifier_1 | WARN: Skipping set up for provider state 'provider in some state' for consumer 'basic_client' as there is no --provider-states-setup-url specified.
provider-verifier_1 | has status code 200
provider-verifier_1 | has a matching body
provider-verifier_1 | Given provider in some state
provider-verifier_1 | request to get non-existent user's phone number
provider-verifier_1 | with GET /phone/micky
provider-verifier_1 | returns a response which
provider-verifier_1 | WARN: Skipping set up for provider state 'provider in some state' for consumer 'basic_client' as there is no --provider-states-setup-url specified.
provider-verifier_1 | has status code 404
provider-verifier_1 |
provider-verifier_1 | 2 interactions, 0 failures
Запуск обеих частей теста в pipeline
Теперь, когда обе части контрактного тестирования разобраны, было бы неплохо запускать их при каждом коммите. Вот где Gitlab CI приходит на помощь. Pipeline jobs описаны в .gitlab-ci.yml
. Прежде чем мы перейдем к pipeline, мы должны сказать несколько слов о GitLab Runner, который является open-source проектом, и используется для запуска jobs и отправки результатов обратно в GitLab. Jobs могут выполняться локально или с использованием Docker-контейнеров. В нашем проекте мы используем Docker. Тестовая инфраструктура реализована в контейнерах и описана в docker-compose.yml
, находящимся в корне проекта.
version: '2'
services:
basic-flask-app:
image: registry.gitlab.com/tknino69/basic_flask_app:latest
ports:
- 5005:5005
postgres:
image: postgres
ports:
- 5432:5432
env_file:
- test-setup.env
volumes:
- db-data:/var/lib/postgresql/data/pgdata
pactbroker:
image: dius/pact-broker
links:
- postgres
ports:
- 80:80
env_file:
- test-setup.env
provider-states:
image: registry.gitlab.com/tknino69/cdc/provider-states:latest
build: provider-states
ports:
- 5000:5000
consumer-test:
image: registry.gitlab.com/tknino69/cdc/consumer-test:latest
command: ["sh", "-c", "find -name '*.pyc' -delete && pytest $${TEST}"]
links:
- pactbroker
environment:
- CONSUMER_VERSION=$CI_COMMIT_SHA
provider-verifier:
image: registry.gitlab.com/tknino69/cdc/provider-verifier:latest
build: provider-verifier
ports:
- 5001:5000
links:
- pactbroker
depends_on:
- consumer-test
- provider-states
command: ['sh', '-c', 'find -name "*.pyc" -delete
&& CONSUMER_VERSION=`curl --header "PRIVATE-TOKEN:$${API_TOKEN}"
https://gitlab.com/api/v4/projects/$${BASIC_CLIENT}/repository/commits | jq ".[0] .id" | sed -e "s/x22//g"`
&& echo $${CONSUMER_VERSION}
&& pact-provider-verifier $${PACT_BROKER}/pacts/provider/$${PROVIDER}/consumer/$${CONSUMER}/version/$${CONSUMER_VERSION}
--provider-base-url=$${BASE_URL}
--pact-broker-base-url=$${PACT_BROKER}
--provider=$${PROVIDER}
--consumer-version-tag=$${CONSUMER_VERSION}
--provider-app-version=$${PROVIDER_VERSION} -v
--publish-verification-results=PUBLISH_VERIFICATION_RESULTS']
environment:
- PROVIDER_VERSION=$CI_COMMIT_SHA
- API_TOKEN=$API_TOKEN
env_file:
- test-setup.env
volumes:
db-data:
Итак, у нас есть сервисы, которые запускаются в контейнерах по мере необходимости.
Сервис провайдера:
basic-flask-app:
image: registry.gitlab.com/tknino69/basic_flask_app:latest
ports:
- 5005:5005
Pact Broker и его БД. Volumes позволяют нам иметь постоянное хранилище для пактов и результатов верификации провайдера:
postgres:
image: postgres
ports:
- 5432:5432
env_file:
- test-setup.env
volumes:
- db-data:/var/lib/postgresql/data/pgdata
pactbroker:
image: dius/pact-broker
links:
- postgres
ports:
- 80:80
env_file:
- test-setup.env
Сервис Provider States. На практике он должен приводить провайдер в определенное состояние (например, завести пользователя в базе данных). Однако в нашем примере он просто выполняет фиктивную функцию.
provider-states:
image: registry.gitlab.com/tknino69/cdc/provider-states:latest
build: provider-states
ports:
- 5000:5000
Сервис, который запускает Consumer Test. Обратите внимание на команду, которая запускается в контейнере find -name '* .pyc' -delete && pytest $$ {TEST}
consumer-test:
image: registry.gitlab.com/tknino69/cdc/consumer-test:latest
command: ["sh", "-c", "find -name '*.pyc' -delete && pytest $${TEST}"]
links:
- pactbroker
environment:
- CONSUMER_VERSION=$CI_COMMIT_SHA
Сервис Provider Verifier:
provider-verifier:
image: registry.gitlab.com/tknino69/cdc/provider-verifier:latest
build: provider-verifier
ports:
- 5001:5000
links:
- pactbroker
depends_on:
- consumer-test
- provider-states
command: ['sh', '-c', 'find -name "*.pyc" -delete
&& CONSUMER_VERSION=`curl --header "PRIVATE-TOKEN:$${API_TOKEN}"
https://gitlab.com/api/v4/projects/$${BASIC_CLIENT}/repository/commits | jq ".[0] .id" | sed -e "s/x22//g"`
&& echo $${CONSUMER_VERSION}
&& pact-provider-verifier $${PACT_BROKER}/pacts/provider/$${PROVIDER}/consumer/$${CONSUMER}/version/$${CONSUMER_VERSION}
--provider-base-url=$${BASE_URL}
--pact-broker-base-url=$${PACT_BROKER}
--provider=$${PROVIDER}
--consumer-version-tag=$${CONSUMER_VERSION}
--provider-app-version=$${PROVIDER_VERSION} -v
--publish-verification-results=PUBLISH_VERIFICATION_RESULTS']
environment:
- PROVIDER_VERSION=$CI_COMMIT_SHA
- API_TOKEN=$API_TOKEN
env_file:
- test-setup.env
Consumer Pipeline
.gitlab-ci.yml
в корне проекта потребителя описывает процессы, которые выполняются на стороне потребителя:
image: gitlab/dind:latest
variables:
TEST: 'tests/docker-compose.app.yml'
CONSUMER_VERSION: $CI_COMMIT_SHA
BASIC_APP: '11993024'
services:
- gitlab/gitlab-runner:latest
before_script:
- docker login -u $GIT_USER -p $GIT_PASS registry.gitlab.com
stages:
- clone_test
- get_broker_up
- test
- verify_provider
- clean_up
clone test:
tags:
- cdc
stage: clone_test
script:
- git clone https://$GIT_USER:$GIT_PASS@gitlab.com/tknino69/cdc.git && ls -ali
artifacts:
paths:
- cdc/
broker:
tags:
- cdc
stage: get_broker_up
script:
- cd cdc && docker-compose -f docker-compose.yml up -d pactbroker
dependencies:
- clone test
test:
tags:
- cdc
stage: test
script:
- cd cdc && CONSUMER_VERSION=$CONSUMER_VERSION docker-compose -f docker-compose.yml -f $TEST up consumer-test
dependencies:
- clone test
provider verification:
tags:
- cdc
stage: verify_provider
script:
- curl -X POST -F token=$CI_JOB_TOKEN -F ref=master https://gitlab.com/api/v4/projects/$BASIC_APP/trigger/pipeline
when: on_success
clean up:
tags:
- cdc
stage: clean_up
script:
- cd cdc && docker-compose stop consumer-test
dependencies:
- clone test
Здесь происходит следующее:
В before_script
мы логинимся в наш реестр gitlab, используя переменные $GIT_USER и $ GIT_PASS, которые мы установили в разделе «Настройки»> «CI / CD»
Provider Pipeline
Конфигурация pipeline провайдера хранится в .gitlab-ci.yml
в корне проекта провайдера.
image: gitlab/dind:latest
variables:
TEST: 'tests/docker-compose.app.yml'
PROVIDER_VERSION: $CI_COMMIT_SHA
services:
- gitlab/gitlab-runner:latest
stages:
- clone_test
- provider_verification
- clean_up
clone test:
tags:
- cdc
stage: clone_test
script:
- git clone https://$GIT_USER:$GIT_PASS@gitlab.com/tknino69/cdc.git
artifacts:
paths:
- cdc/
verify provider:
tags:
- cdc
stage: provider_verification
before_script:
- cd cdc
- docker login -u $GIT_USER -p $GIT_PASS registry.gitlab.com && docker-compose -f docker-compose.yml up -d basic-flask-app
script:
- PROVIDER_VERSION=$PROVIDER_VERSION docker-compose -f docker-compose.yml -f $TEST up provider-verifier
dependencies:
- clone test
.clean up:
tags:
- cdc
stage: clean_up
script:
- cd cdc && docker-compose down --rmi local
Так же как и в Consumer Pipeline, у нас есть несколько jobs:
Суммируем:
Спасибо за внимание.
Автор: tknino
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/317144
Ссылки в тексте:
[1] Хабре: https://habr.com/ru/search/?q=consumer+driven+contracts#h
[2] пирамиде автотестирования: https://martinfowler.com/articles/practical-test-pyramid.html
[3] Pact: https://docs.pact.io/
[4] теста на стороне потребителя: https://docs.pact.io/how_pact_works#consumer-testing
[5] Pactman: https://github.com/reecetech/pactman
[6] Источник: https://habr.com/ru/post/451132/?utm_source=habrahabr&utm_medium=rss&utm_campaign=451132
Нажмите здесь для печати.