Los objetivos de esta publicación son :
- Una breve introducción a los contratos impulsados por el consumidor (CDC)
- Configurar la canalización de CI basada en CDI
Contratos dirigidos por el consumidor
En esta parte, repasaremos los puntos principales de los CDC. Este artículo no es exhaustivo sobre el tema de las pruebas contractuales. Hay una cantidad suficiente de materiales sobre este tema en el mismo Habré .
Para continuar, debemos familiarizarnos con las principales disposiciones de los CDC:
- Las pruebas de contacto están en el nivel de Pruebas de servicio / integración por encima de las Pruebas de unidad de acuerdo con la pirámide de Mike Cohn .
- Las pruebas de contrato se pueden aplicar cuando hay 2 (o más) servicios que interactúan entre sí.
- El enfoque orientado al consumidor significa que el primer paso en la implementación es escribir una prueba en el lado del consumidor. El resultado de la prueba es un pacto (contrato) en formato json que describe la interacción entre el consumidor (por ejemplo, interfaz web / interfaz móvil: un servicio que quiere recibir algunos datos) y el proveedor (por ejemplo, API del servidor: un servicio que proporciona datos)
- El siguiente paso es verificar el contrato con el proveedor. Esto está completamente implementado por el marco del Pacto .
Entonces, comencemos con la prueba del lado del consumidor . Usé Pactman . Así es como se ve la prueba:
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)
Usando Pact DSL, describimos las interacciones de solicitud / respuesta. Después de comenzar la prueba, obtenemos un nuevo archivo ({consumidor} - {proveedor} -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" } } }
A continuación, debemos pasar el pacto al proveedor para su verificación. Esto se hace usando Pact Broker.
Pact Broker es un repositorio de contratos con algunas características adicionales que nos permiten rastrear la compatibilidad de las versiones de servicio, así como generar diagramas de red (interacción de servicio).
Corredor de pacto

El pacto

Matriz de versiones

Verificación de proveedor
Esta parte de la prueba la realiza completamente el marco. Después de la verificación, los resultados se envían de vuelta a 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
Ejecutar ambas partes de la prueba en la tubería
Ahora que ambas partes de la prueba del contrato se han desmontado, sería bueno ejecutarlas en cada confirmación. Aquí es donde Gitlab CI viene al rescate. Los trabajos de .gitlab-ci.yml
se describen en .gitlab-ci.yml
. Antes de pasar a la tubería, necesitamos decir algunas palabras sobre el GitLab Runner, que es un proyecto de código abierto, y se utiliza para ejecutar trabajos y enviar los resultados a GitLab. Los trabajos se pueden ejecutar localmente o utilizando contenedores acoplables. En nuestro proyecto usamos Docker. La infraestructura de prueba se implementa en contenedores y se describe en docker-compose.yml
, ubicado en la raíz del proyecto.
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:
Entonces, tenemos servicios que se ejecutan en contenedores según sea necesario.
Proveedor de servicios:
basic-flask-app: image: registry.gitlab.com/tknino69/basic_flask_app:latest ports: - 5005:5005
Pact Broker y su base de datos. Los volúmenes nos permiten tener un repositorio permanente para pactos y resultados de verificación de proveedores:
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
Estados proveedores de servicios. En la práctica, debe llevar al proveedor a un cierto estado (por ejemplo, colocar al usuario en la base de datos). Sin embargo, en nuestro ejemplo, simplemente realiza una función ficticia.
provider-states: image: registry.gitlab.com/tknino69/cdc/provider-states:latest build: provider-states ports: - 5000:5000
Un servicio que ejecuta la Prueba del consumidor. Preste atención al comando que se ejecuta en el contenedor 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
Verificador del proveedor de servicios:
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
Tubería de consumo
.gitlab-ci.yml
en la raíz del proyecto del consumidor describe los procesos que se ejecutan en el lado del consumidor:
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
Aquí sucede lo siguiente:
En before_script
iniciamos sesión en nuestro registro de gitlab usando las variables $ GIT_USER y $ GIT_PASS que configuramos en Configuración> CI / CD

- A continuación, clonamos un proyecto de prueba
- En el siguiente paso, levantamos Pact Broker
- Entonces comienza la Prueba del consumidor.
- Después de eso, use la API de Gitlab para iniciar la verificación del proveedor.
- Y finalmente nos limpiamos
Proveedor pipipeline
La configuración de la tubería del proveedor se almacena en .gitlab-ci.yml
en la raíz del proyecto del proveedor.
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
Al igual que en la cartera de consumidores, tenemos varios trabajos:
- Clonar un proyecto de prueba
- Verificar proveedor
- Nos limpiamos
Resumir
- Escribió una prueba de contrato en Python
- Configurar un entorno de prueba en contenedores Docker
- CI configurado en base a pruebas de contrato, es decir comprometerse con el proyecto del consumidor lanzará la tubería de CI ( en el lado del consumidor : clonar el entorno de prueba -> iniciar Pact Broker -> probar el consumidor -> iniciar la verificación del proveedor -> limpiar; en el lado del proveedor : clonar el entorno de prueba -> verificación del proveedor -> limpiar )
Comprometerse con el proyecto del proveedor inicia la verificación del proveedor para garantizar que el proveedor cumpla con el pacto
Gracias por su atencion